package jp.sourceforge.nicoro;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicLineFormatter;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.widget.Toast;

import static jp.sourceforge.nicoro.Log.LOG_TAG;;

/**
 * 関数群
 */
public class Util {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;

	private static final int DEFAULT_TIMEOUT_MS = 180 * 1000;

	public static String getFirstHeaderValue(HttpMessage httpMessage, String name) {
		Header header = httpMessage.getFirstHeader(name);
		if (header != null) {
			return header.getValue();
		} else {
			return null;
		}
	}

	/**
	 * 指定パスのファイルからJSONを作成
	 * @param path JSONファイルのパス
	 * @return ファイルから生成したJSONObject。読み込み失敗時はnull
	 */
	public static JSONObject createJSONFromFile(String path) {
	    return createJSONFromFile(new File(path));
	}
	/**
	 * 指定パスのファイルからJSONを作成
	 * @param file JSONファイル
     * @return ファイルから生成したJSONObject。読み込み失敗時はnull
	 */
	public static JSONObject createJSONFromFile(File file) {
		FileReader reader = null;
		JSONObject json = null;

		StringBuilder readString = new StringBuilder(1024);
		char[] buf = new char[1024];
		final int retry = 3;
		int i = 0;
		while (true) {
			try {
				reader = new FileReader(file);
				while (true) {
					int readLength = reader.read(buf, 0, buf.length);
					if (readLength < 0) {
						break;
					}
					readString.append(buf, 0, readLength);
				}
				json = new JSONObject(readString.toString());
				break;
			} catch (FileNotFoundException e) {
				// ファイルが無いときは問題なし
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, e.toString());
				}
				break;
			} catch (IOException e) {
				Log.d(LOG_TAG, e.toString(), e);
				break;
			} catch (JSONException e) {
				// 書き込み中でデータ壊れているのかもしれないのでリトライ
				Log.d(LOG_TAG, e.toString(), e);
			} finally {
				if (reader != null) {
					try {
						reader.close();
					} catch (IOException e) {
						Log.d(LOG_TAG, e.toString(), e);
					}
				}
			}
			++i;
			if (i >= retry) {
				break;
			}
			readString.setLength(0);
			SystemClock.sleep(50L);
		}
		return json;
	}

	/**
	 * JSONを指定パスのファイルに保存
	 * @param path
	 * @param json
	 * @return trueで成功、falseで失敗
	 */
	public static boolean saveJSONToFile(String path, JSONObject json) {
		File tgtFile = new File(path);
		File bakFile = new File(path + ".bak");

		OutputStreamWriter writer = null;
		FileOutputStream stream = null;
		boolean result = false;
		try {
			if (tgtFile.exists()) {
				copyFile(tgtFile, bakFile);
			}

			stream = new FileOutputStream(tgtFile);
			writer = new OutputStreamWriter(stream);
			writer.write(json.toString());
			result = true;
			bakFile.delete();
			stream.getFD().sync();
		} catch (IOException e) {
			Log.d(LOG_TAG, e.toString(), e);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
			}
			if (stream != null) {
				try {
					stream.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
			}
		}
		return result;
	}

	public static void logHeaders(String tag, Header[] headers) {
		for (Header header : headers) {
			Log.d(tag, Log.buf().append(header.getName()).append(": ")
					.append(header.getValue()).toString());
		}
	}

	public static AlertDialog createCloseDialog(Activity activity,
            String title, String message,
            DialogInterface.OnClickListener onClickListener,
            DialogInterface.OnCancelListener onCancelListener) {
        return new AlertDialog.Builder(activity)
            .setTitle(title)
            .setMessage(message)
            .setCancelable(true)
            .setNeutralButton(android.R.string.ok, onClickListener)
            .setOnCancelListener(onCancelListener)
            .create();
	}

	public static void showCloseDialog(final Activity activity,
			String title, String message, final boolean tryFinishActivity) {
	    createCloseDialog(activity, title, message,
	            new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
				if (tryFinishActivity) {
					activity.finish();
				}
			}
		}, new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                dialog.dismiss();
                if (tryFinishActivity) {
                    activity.finish();
                }
            }
		}).show();
	}
	public static void showCloseDialog(final Activity activity,
			int titleResID, int messageResId, final boolean tryFinishActivity) {
		showCloseDialog(activity, activity.getString(titleResID),
				activity.getString(messageResId), tryFinishActivity);
	}

	public static void showErrorDialog(final Activity activity,
			String errorMessage, final boolean tryFinishActivity) {
		showCloseDialog(activity, activity.getString(R.string.error),
				errorMessage, tryFinishActivity);
	}
	public static void showErrorDialog(final Activity activity,
			int errorMessageResID, final boolean tryFinishActivity) {
		showErrorDialog(activity,
				activity.getString(errorMessageResID),
				tryFinishActivity);
	}

	public static void showListDialog(final Activity activity,
			CharSequence title, CharSequence[] items,
			DialogInterface.OnClickListener listener) {
		new AlertDialog.Builder(activity)
			.setCancelable(true)
			.setTitle(title)
			.setItems(items, listener)
			.show();
	}

	public static Toast createErrorToast(Context context, String errorMessage) {
        Toast toast = Toast.makeText(context,
                errorMessage,
                Toast.LENGTH_LONG);
        toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
                0, 50);
	    return toast;
	}
    public static Toast createInfoToast(Context context, String info) {
        // 流用
        return createErrorToast(context, info);
    }
    public static Toast createInfoToast(Context context, int infoResID) {
        // 流用
        return createErrorToast(context, context.getResources().getString(infoResID));
    }

	public static void showErrorToast(Context context, String errorMessage) {
	    createErrorToast(context, errorMessage).show();
	}
	public static void showErrorToast(Context context, int errorMessageResID) {
		showErrorToast(context, context.getResources().getString(errorMessageResID));
	}

	public static void showInfoToast(Context context, String info) {
		// 流用
		showErrorToast(context, info);
	}
	public static void showInfoToast(Context context, int infoResID) {
		// 流用
		showErrorToast(context, context.getResources().getString(infoResID));
	}

	public static String getCookieValueFromManager(String urlBase, String name) {
		CookieManager cookieManager = CookieManager.getInstance();
		cookieManager.removeExpiredCookie();
		String cookie = cookieManager.getCookie(urlBase);
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("getCookieValueFromManager: urlBase=")
					.append(urlBase).append(" cookie=").append(cookie).toString());
		}
		if (cookie == null) {
			return null;
		}
		Matcher matcher = Pattern.compile("(" + name + "=[^;\\n\\r]+)").matcher(cookie);
		if (matcher.find()) {
			return matcher.group(1);
		}
		return null;
	}

	public static String getCookieValueFromManager(String urlBase, Matcher matcher) {
		CookieManager cookieManager = CookieManager.getInstance();
		cookieManager.removeExpiredCookie();
		String cookie = cookieManager.getCookie(urlBase);
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("getCookieValueFromManager: urlBase=")
					.append(urlBase).append(" cookie=").append(cookie).toString());
		}
		matcher.reset(cookie);
		if (matcher.find()) {
			return matcher.group(1);
		}
		return null;
	}

	public static DefaultHttpClient createHttpClient() {
		return createHttpClient(DEFAULT_TIMEOUT_MS);
	}

	public static DefaultHttpClient createHttpClient(int timeout) {
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
		SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
		schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

		HttpParams httpParams = new BasicHttpParams();
		HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);
		HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
		HttpConnectionParams.setSoTimeout(httpParams, timeout);
		DefaultHttpClient httpClient = new DefaultHttpClient(
				new ThreadSafeClientConnManager(httpParams, schemeRegistry),
				httpParams);
		return httpClient;
	}

	public static Matcher getMatcher(Matcher matcher, String pattern, CharSequence input) {
		if (matcher == null) {
			return Pattern.compile(pattern).matcher(input);
		} else {
			return matcher.reset(input);
		}
	}

	/**
	 *
	 * ネットワークからの情報取得が完了するまで処理が返らないので注意
	 *
	 * @param httpClient
	 * @param hostname
	 * @param uri
	 * @param cookie
	 * @param userAgent
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static String getSingleLineDataFromAPI(
			DefaultHttpClient httpClient, String hostname, String uri,
			String cookie, String userAgent) throws ClientProtocolException, IOException {
		HttpUriRequest httpRequest = createRequestGetSingleLineDataFromAPI(
				uri, cookie, userAgent);
		return getSingleLineDataFromAPI(httpClient, hostname, httpRequest);
	}

	/**
	 *
	 * ネットワークからの情報取得が完了するまで処理が返らないので注意
	 *
	 * @param httpClient
	 * @param hostname
	 * @param httpRequest
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static String getSingleLineDataFromAPI(
			DefaultHttpClient httpClient, String hostname,
			HttpUriRequest httpRequest) throws ClientProtocolException, IOException {
		String result = null;
		HttpResponse httpResponse = httpClient.execute(
				new HttpHost(hostname, 80),
				httpRequest
				);
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, "==========getSingleLineDataFromAPI httpResponse==========");
            Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(httpResponse.getStatusLine(), null));
			Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
			Log.d(LOG_TAG, "==========httpResponse end==========");
		}
		if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append(hostname)
						.append(httpRequest.getURI().toString())
						.append(" entity>>>").toString());
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			result = "";
			LineNumberReader reader = null;
			try {
				reader = new LineNumberReader(new InputStreamReader(httpEntity.getContent()));
				while (true) {
					String line = reader.readLine();
					if (line == null) {
						break;
					}
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, line);
					}
					try {
					    result += URLDecoder.decode(line, HTTP.UTF_8);
					} catch (IllegalArgumentException e) {
					    Log.e(LOG_TAG, Log.buf().append(e.toString())
					            .append(" : ").append(line)
					            .toString(), e);
					    // エラー時は処理中断
					    break;
					}
				}
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "<<<entity end");
					Log.d(LOG_TAG, Log.buf().append("result=").append(result).toString());
				}
			} finally {
				if (reader != null) {
					reader.close();
				}
			}
		}
		return result;
	}

	public static HttpUriRequest createRequestGetSingleLineDataFromAPI(
			String uri, String cookie, String userAgent) {
		HttpUriRequest httpRequest = new HttpGet(uri);
		httpRequest.addHeader("Cookie", cookie);
		if (userAgent != null) {
			httpRequest.setHeader("User-Agent", userAgent);
		}
		return httpRequest;
	}


	public static String getFirstMatch(Matcher matcher) {
		if (matcher.find()) {
			return matcher.group(1);
		}
		return null;
	}

	public static String getFirstMatch(String input, String patternString) {
		Matcher matcher = Pattern.compile(patternString).matcher(input);
		return getFirstMatch(matcher);
	}

	public static int readComplete(InputStream in, byte[] b, int offset, int length) throws IOException {
		int readSize = 0;
		while (readSize < length) {
			int ret = in.read(b, offset + readSize, length - readSize);
			if (ret < 0) {
				return ret;
			}
			readSize += ret;
		}
		assert readSize == length;
		return readSize;
	}
	public static int readComplete(InputStream in, byte[] b) throws IOException {
		return readComplete(in, b, 0, b.length);
	}

	public static int readComplete(RandomAccessFile in, byte[] b, int offset, int length) throws IOException {
		int readSize = 0;
		while (readSize < length) {
			int ret = in.read(b, offset + readSize, length - readSize);
			if (ret < 0) {
				return ret;
			}
			readSize += ret;
		}
		assert readSize == length;
		return readSize;
	}
	public static int readComplete(RandomAccessFile in, byte[] b) throws IOException {
		return readComplete(in, b, 0, b.length);
	}

	public static int readByte(InputStream in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readByte");
		}
		return temp;
	}
	public static int readShortLE(InputStream in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readShortLE");
		}
		int ret = (temp & 0xff);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readShortLE");
		}
		ret |= ((temp & 0xff) << 8);

		return ret;
	}
	public static int readIntLE(InputStream in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		int ret = (temp & 0xff);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 8);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 16);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 24);

		return ret;
	}

	public static int readByte(RandomAccessFile in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readByte");
		}
		return temp;
	}
	public static int readShortLE(RandomAccessFile in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readShortLE");
		}
		int ret = (temp & 0xff);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readShortLE");
		}
		ret |= ((temp & 0xff) << 8);

		return ret;
	}
	public static int readIntLE(RandomAccessFile in) throws EOFException, IOException {
		int temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		int ret = (temp & 0xff);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 8);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 16);
		temp = in.read();
		if (temp < 0) {
			throw new EOFException("Util#readIntLE");
		}
		ret |= ((temp & 0xff) << 24);

		return ret;
	}

	/**
	 * 型推論を使用したHashMapの生成
	 * @param <K>
	 * @param <V>
	 * @return
	 */
	public static <K, V> HashMap<K, V> newHashMap() {
		return new HashMap<K, V>();
	}
	/**
	 * 型推論を使用したHashMapの生成
	 * @param <K>
	 * @param <V>
	 * @param capacity	the initial capacity of this {@code HashMap}.
	 * @return
	 */
	public static <K, V> HashMap<K, V> newHashMap(int capacity) {
		return new HashMap<K, V>(capacity);
	}

	/**
	 * 型推論を使用したArrayListの生成
	 * @param <K>
	 * @param <V>
	 * @return
	 */
	public static <E> ArrayList<E> newArrayList() {
		return new ArrayList<E>();
	}
	/**
	 * 型推論を使用したArrayListの生成
	 * @param <K>
	 * @param <V>
	 * @param capacity	the initial capacity of this {@code ArrayList}.
	 * @return
	 */
	public static <E> ArrayList<E> newArrayList(int capacity) {
		return new ArrayList<E>(capacity);
	}

	public static boolean copyFile(File src, File dst) {
		FileInputStream srcStream = null;
		FileOutputStream dstStream = null;
		try {
			srcStream = new FileInputStream(src);
			dstStream = new FileOutputStream(dst);
			FileChannel srcChannel = srcStream.getChannel();
			FileChannel dstChannel = dstStream.getChannel();
			long srcChannelSize = srcChannel.size();
			if (srcChannelSize > 0) {
				dstChannel.transferFrom(srcChannel, 0, srcChannelSize);
			}
			dstStream.getFD().sync();
			return true;
		} catch (FileNotFoundException e) {
			Log.d(LOG_TAG, e.toString(), e);
		} catch (IOException e) {
			Log.d(LOG_TAG, e.toString(), e);
		} finally {
			// StreamのcloseでChannelも閉じる
			if (srcStream != null) {
				try {
					srcStream.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
			}
			if (dstStream != null) {
				try {
					dstStream.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
			}
		}
		return false;
	}

	public static StringBuilder loadRawJs(StringBuilder builder, Resources res,
			int id) {
		InputStream in = res.openRawResource(id);
		InputStreamReader reader = new InputStreamReader(in);
		try {
			char[] buf = new char[256];
			builder.append("javascript: ");
			while (true) {
				int read = reader.read(buf);
				if (read < 0) {
					break;
				}
				builder.append(buf, 0, read);
			}
		} catch (IOException e) {
			Log.e(LOG_TAG, e.toString(), e);
		} finally {
			try {
    			reader.close();
			} catch (IOException e) {
				Log.d(LOG_TAG, e.toString(), e);
			}
		}
		return builder;
	}

	public static StringBuilder appendPlayTime(StringBuilder builder,
			int minutes, int seconds) {
		if (minutes < 10) {
			builder.append("00");
		}
		else if (minutes < 100) {
			builder.append('0');
		}
		builder.append(minutes).append(':');
		if (seconds < 10) {
			builder.append('0');
		}
		return builder.append(seconds);
	}

    public static StringBuilder appendPlayTime(StringBuilder builder,
            int hour, int minutes, int seconds) {
        if (hour != 0) {
            builder.append(hour).append(':');
        }
        if (minutes < 10) {
            builder.append('0');
        }
        builder.append(minutes).append(':');
        if (seconds < 10) {
            builder.append('0');
        }
        return builder.append(seconds);
    }

	/**
	 * ネットワークからBitmapをダウンロードする。
	 * ネットワークからの情報取得が完了するまで処理が返らないので注意
	 *
	 * @param url
	 * @return
	 */
	public static Bitmap loadBitmap(DefaultHttpClient httpClient,
			HttpUriRequest httpRequest
			) throws ClientProtocolException, IOException {
		InputStream inDownload = null;
		try {
			HttpResponse httpResponse = httpClient.execute(
			        httpRequest);
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, "----- loadBitmap httpResponse start -----");
                Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(httpResponse.getStatusLine(), null));
				Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
				Log.d(LOG_TAG, "----- loadBitmap httpResponse end -----");
			}
			if (httpResponse.getStatusLine().getStatusCode()
					== HttpStatus.SC_OK) {
				HttpEntity httpEntity = httpResponse.getEntity();
				inDownload = httpEntity.getContent();
				inDownload = new FlushedInputStream(inDownload);
				Bitmap bitmap = BitmapFactory.decodeStream(inDownload);
				return bitmap;
			}
		} finally {
			if (inDownload != null) {
				try {
					inDownload.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
			}
		}
		return null;
	}

	// "Multithreading For Performance" を参考に
	public static class FlushedInputStream extends FilterInputStream {
		public FlushedInputStream(InputStream in) {
			super(in);
		}

		@Override
		public long skip(long n) throws IOException {
	        long totalBytesSkipped = 0L;
	        while (totalBytesSkipped < n) {
	            long bytesSkipped = in.skip(n - totalBytesSkipped);
	            if (bytesSkipped == 0L) {
	                int b = read();
	                if (b < 0) {
	                    break;  // we reached EOF
	                } else {
	                    bytesSkipped = 1; // we read one byte
	                }
	            }
	            totalBytesSkipped += bytesSkipped;
	        }
	        return totalBytesSkipped;
		}
	}

	public static ProgressDialog createProgressDialogLoading(Activity activity,
			CharSequence message,
			DialogInterface.OnCancelListener onCancelListener) {
        ProgressDialog pd = new ProgressDialog(activity);
        pd.setMessage(message);
        pd.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        if (onCancelListener == null) {
	        pd.setCancelable(false);
        } else {
	        pd.setCancelable(true);
	        pd.setOnCancelListener(onCancelListener);
        }
        return pd;
	}

	public static ProgressDialog createProgressDialogLoading(Activity activity,
			int messageResID,
			DialogInterface.OnCancelListener onCancelListener) {
		return createProgressDialogLoading(activity,
				activity.getString(messageResID), onCancelListener);
	}

    public static String convertHtmlSpecialCharacter(String src) {
        return src.replaceAll("&lt;", "<")
            .replaceAll("&rt;", ">")
            .replaceAll("&nbsp;", ">")
            .replaceAll("&amp;", "&");
    }

    public static void replaceView(View before, View after) {
        ViewGroup parent = (ViewGroup) before.getParent();
        int index = parent.indexOfChild(before);
        assert index >= 0;
        parent.removeViewAt(index);
        parent.addView(after, index);
    }

    public static boolean isInMainThread() {
        return Thread.currentThread() == Looper.getMainLooper().getThread();
    }
    public static boolean isInMainThread(Context context) {
        return Thread.currentThread() == context.getMainLooper().getThread();
    }
}
