package jp.sourceforge.nicoro;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import jp.gr.java_conf.shiseissi.commonlib.FileUtil;
import jp.gr.java_conf.shiseissi.commonlib.ThreadSoftReference;
import jp.gr.java_conf.shiseissi.commonlib.ThreadUtil;

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.StatusLine;
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 org.xmlpull.v1.XmlPullParser;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
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_CONNECTION_TIMEOUT_MS = 60 * 1000;
    private static final int DEFAULT_SO_TIMEOUT_MS = 60 * 1000;

    public static final String KEY_ARGUMENTS = "KEY_ARGUMENTS";
    public static final String KEY_EXTRAS = "KEY_EXTRAS";

    private static volatile int sWifiModeFullHigh = 0;

	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) {
				    FileUtil.closeIgnoreException(reader);
				}
			}
			++i;
			if (i >= retry) {
				break;
			}
			readString.setLength(0);
			SystemClock.sleep(50L);
		}
		return json;
	}

	private static ThreadSoftReference<CharsetEncoder> sCharsetEncoderJSONCache =
	    new ThreadSoftReference<CharsetEncoder>();

	/**
	 * 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);
//			}
            tgtFile.renameTo(bakFile);

            CharsetEncoder ce = sCharsetEncoderJSONCache.getValue();
            if (ce == null) {
                ce = Charset.forName("UTF-8").newEncoder()
                    .onMalformedInput(CodingErrorAction.REPLACE)
                    .onUnmappableCharacter(CodingErrorAction.REPLACE);
                sCharsetEncoderJSONCache.setValue(ce);
            }
            ce.reset();

			stream = new FileOutputStream(tgtFile);
			writer = new OutputStreamWriter(stream, ce);
			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);
				} catch (IllegalStateException e) {
				    // CharsetEncoder#flushで出る可能性あり
                    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 AlertDialog createCloseDialog(final Activity activity,
            String title, String message, final boolean tryFinishActivity) {
	    return 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();
                }
            }
		});
	}

    public static AlertDialog showCloseDialog(final Activity activity,
            String title, String message, final boolean tryFinishActivity) {
        AlertDialog dialog = createCloseDialog(activity, title, message, tryFinishActivity);
        dialog.show();
        return dialog;
    }

	public static AlertDialog showCloseDialog(final Activity activity,
			int titleResID, int messageResId, final boolean tryFinishActivity) {
		return showCloseDialog(activity, activity.getString(titleResID),
				activity.getString(messageResId), tryFinishActivity);
	}

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

	public static AlertDialog showListDialog(final Activity activity,
			CharSequence title, CharSequence[] items,
			DialogInterface.OnClickListener listener) {
		return 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_CONNECTION_TIMEOUT_MS,
		        DEFAULT_SO_TIMEOUT_MS);
	}

	/**
	 *
	 * @param connectionTimeout 接続が確立するまで待つときのタイムアウト設定
	 * @param soTimeout データが来るまで待つときのタイムアウト設定
	 * @return
	 */
	public static DefaultHttpClient createHttpClient(int connectionTimeout,
	        int soTimeout) {
		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, connectionTimeout);
		HttpConnectionParams.setSoTimeout(httpParams, soTimeout);
		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 = HttpManager.executeWrapper(httpClient,
		        new HttpHost(hostname, 80), httpRequest);
        StatusLine statusLine = httpResponse.getStatusLine();
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, "==========getSingleLineDataFromAPI httpResponse==========");
            Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(statusLine, null));
			Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
			Log.d(LOG_TAG, "==========httpResponse end==========");
		}
		if (statusLine.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();
			StringBuilder builder = new StringBuilder();
			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.dLong(LOG_TAG, line);
					}
					try {
					    builder.append(URLDecoder.decode(line, HTTP.UTF_8));
					} catch (IllegalArgumentException e) {
					    Log.e(LOG_TAG, Log.buf().append(e.toString())
					            .append(" : ").append(line)
					            .toString(), e);
					    // エラー時は処理中断
					    break;
					}
				}
				result = builder.toString();
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "<<<entity end");
					Log.dLong(LOG_TAG, Log.buf().append("result=").append(result).toString());
				}
			} finally {
				if (reader != null) {
					reader.close();
				}
			}
		}
		return result;
	}

    /**
     *
     * ネットワークからの情報取得が完了するまで処理が返らないので注意
     *
     * @param httpClient
     * @param hostname
     * @param httpRequest
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static String getSingleLineDataFromAPIWithoutDecode(
            DefaultHttpClient httpClient, String hostname,
            HttpUriRequest httpRequest) throws ClientProtocolException, IOException {
        String result = null;
        HttpResponse httpResponse = HttpManager.executeWrapper(httpClient,
                new HttpHost(hostname, 80), httpRequest);
        StatusLine statusLine = httpResponse.getStatusLine();
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "==========getSingleLineDataFromAPIWithoutDecode httpResponse==========");
            Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(statusLine, null));
            Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
            Log.d(LOG_TAG, "==========httpResponse end==========");
        }
        if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append(hostname)
                        .append(httpRequest.getURI().toString())
                        .toString());
            }
            HttpEntity httpEntity = httpResponse.getEntity();
            StringBuilder builder = new StringBuilder();
            LineNumberReader reader = null;
            try {
                reader = new LineNumberReader(new InputStreamReader(httpEntity.getContent()));
                while (true) {
                    String line = reader.readLine();
                    if (line == null) {
                        break;
                    }
                    builder.append(line);
                }
                result = builder.toString();
                if (DEBUG_LOGD) {
                    Log.dLong(LOG_TAG, Log.buf().append("result=").append(result).toString());
                }
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }
        }
        return result;
    }

	/**
     *
     * ネットワークからの情報取得が完了するまで処理が返らないので注意
     *
	 * @param httpClient
	 * @param hostname
	 * @param httpRequest
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @throws JSONException
	 */
    public static JSONObject getJSONFromAPI(
            DefaultHttpClient httpClient, String hostname,
            HttpUriRequest httpRequest) throws ClientProtocolException, IOException, JSONException {
        JSONObject result = null;
        HttpResponse httpResponse = HttpManager.executeWrapper(httpClient,
                new HttpHost(hostname, 80), httpRequest);
        StatusLine statusLine = httpResponse.getStatusLine();
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "==========getJSONFromAPI httpResponse==========");
            Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(statusLine, null));
            Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
            Log.d(LOG_TAG, "==========httpResponse end==========");
        }
        if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append(hostname)
                        .append(httpRequest.getURI().toString())
                        .toString());
            }
            HttpEntity httpEntity = httpResponse.getEntity();
            StringBuilder data = new StringBuilder();
            InputStreamReader reader = null;
            try {
                reader = new InputStreamReader(httpEntity.getContent(), HTTP.UTF_8);
                char[] buf = new char[2048];
                while (true) {
                    int read = reader.read(buf);
                    if (read < 0) {
                        break;
                    }
                    data.append(buf, 0, read);
                }

                result = new JSONObject(data.toString());
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "result>>>");
                    try {
                        Log.dLong(LOG_TAG, result.toString());
                    } catch (OutOfMemoryError e) {
                        Log.e(LOG_TAG, e.toString(), e);
                    }
                    Log.d(LOG_TAG, "<<< result end");
                }
            } 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 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 = HttpManager.executeWrapper(httpClient,
			        httpRequest);
            StatusLine statusLine = httpResponse.getStatusLine();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, "----- loadBitmap httpResponse start -----");
                Log.d(LOG_TAG, BasicLineFormatter.formatStatusLine(statusLine, null));
				Util.logHeaders(LOG_TAG, httpResponse.getAllHeaders());
				Log.d(LOG_TAG, "----- loadBitmap httpResponse end -----");
			}
			if (statusLine.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);
	}

	/**
	 * HTML特殊文字を含む文字列を普通の文字列に変換する
	 * @param src
	 * @return
	 */
    public static String convertHtmlEscapedCharacter(String src) {
        if (src == null) {
            return null;
        }
        int esc = src.indexOf('&');
        if (esc < 0) {
            // 特殊文字含まず
            return src;
        }
        int srcLength = src.length();
        StringBuilder builder = new StringBuilder(srcLength);
        int index = 0;
        while (true) {
            builder.append(src, index, esc);
            index = esc + 1;
            int escEnd = src.indexOf(';', index);
            int escNext = src.indexOf('&', index);
            if (escNext >= 0) {
                if (escNext < escEnd) {
                    // 特殊文字でないので普通に追加
                    index = esc;
                    esc = escNext;
                } else if (escEnd < 0) {
                    // 特殊文字残っていない
                    builder.append(src, esc, srcLength);
                    break;
                } else {
                    // 特殊文字判定
                    String text = src.substring(index, escEnd);
                    if (processHtmlEscapedCharacter(builder, text)) {
                        index = escEnd + 1;
                    } else {
                        // 特殊文字でないので普通に追加
                        index = esc;
                    }
                    esc = escNext;
                }
            } else {
                if (escEnd < 0) {
                    // 特殊文字残っていない
                    builder.append(src, esc, srcLength);
                    break;
                } else {
                    // 特殊文字判定
                    String text = src.substring(index, escEnd);
                    if (processHtmlEscapedCharacter(builder, text)) {
                        index = escEnd + 1;
                    } else {
                        // 特殊文字でないので普通に追加
                        index = esc;
                    }
                    // 特殊文字残っていないのでここで全部追加
                    builder.append(src, index, srcLength);
                    break;
                }
            }
        }
        return builder.toString();
    }

    private static boolean processHtmlEscapedCharacter(StringBuilder builder,
            String text) {
        int textLength = text.length();
        if (textLength == 0) {
            // 特殊文字でない
            return false;
        } else if (text.charAt(0) == '#') {
            try {
                int cp;
                if (textLength >= 2 && text.charAt(1) == 'x') {
                    // 16進数
                    cp = Integer.parseInt(text.substring(2, textLength), 16);
                } else {
                    // 10進数
                    cp = Integer.parseInt(text.substring(1, textLength), 10);
                }
                builder.append(Character.toChars(cp));
                return true;
            } catch (NumberFormatException e) {
                // 変換失敗は特殊文字ではない
                return false;
            } catch (IllegalArgumentException e) {
                // 変換失敗は特殊文字ではない
                return false;
            }
        } else if ("quot".equals(text)) {
            builder.append('"');
            return true;
        } else if ("amp".equals(text)) {
            builder.append('&');
            return true;
        } else if ("lt".equals(text)) {
            builder.append('<');
            return true;
        } else if ("gt".equals(text)) {
            builder.append('>');
            return true;
        } else if ("nbsp".equals(text)) {
            builder.append(' ');
            return true;
        } else {
            // 特殊文字でない
            return false;
        }
    }

    public static StringBuilder appendLogXmlIndent(StringBuilder buf,
            XmlPullParser pullParser) {
        int depth = pullParser.getDepth();
        for (int i = 0; i < depth; ++i) {
            buf.append(' ');
        }
        return buf;
    }
    public static StringBuilder appendLogXmlAttribute(StringBuilder buf,
            XmlPullParser pullParser) {
        int count = pullParser.getAttributeCount();
        for (int i = 0; i < count; ++i) {
            buf.append(' ').append(pullParser.getAttributeName(i))
                .append('=').append(pullParser.getAttributeValue(i));
        }
        return buf;
    }

    /**
     * 型推論を使用した {@link Bundle#getSerializable} の実行
     * @param <T>
     * @param bundle
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T getSerializable(Bundle bundle, String key) {
        return (T) bundle.getSerializable(key);
    }

    /**
     * {@link Util#getRunCachingProgressMessage} で使用するDecimalFormatを作成
     * @return
     */
    public static DecimalFormat createRunCachingProgressMessageFormat() {
        DecimalFormat decimalFormatMB = new DecimalFormat();
        decimalFormatMB.applyPattern("0");
        decimalFormatMB.setMaximumFractionDigits(2);
        decimalFormatMB.setMinimumFractionDigits(2);
        decimalFormatMB.setMinimumIntegerDigits(3);
        return decimalFormatMB;
    }
    /**
     * 実行中キャッシュの情報を文字列で取得
     * @param decimalFormatMB {@link Util#createRunCachingProgressMessageFormat}で作成したもの
     * @param buffer 出力先
     * @param seekOffsetWrite ファイルの読み込み済み部分のサイズ
     * @param contentLength ファイルの全長サイズ
     * @return buffer
     */
    public static StringBuffer getRunCachingProgressMessage(
            DecimalFormat decimalFormatMB, StringBuffer buffer,
            int seekOffsetWrite, int contentLength) {
        if (seekOffsetWrite >= 0 && contentLength >= 0) {
            float seekOffsetWriteMB = seekOffsetWrite / (float) (1024 * 1024);
            float contentLengthMB = contentLength / (float) (1024 * 1024);
            FieldPosition fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
            decimalFormatMB.format(seekOffsetWriteMB, buffer, fp);
            buffer.append("/");
            decimalFormatMB.format(contentLengthMB, buffer, fp);
            return buffer.append("MB");
        } else {
            return buffer.append("---.--/---.--MB");
        }
    }

    /**
     * JSONObjectからStringを取得する。char[]は必要以上に共有しない
     * @param json
     * @param name
     * @return
     * @throws JSONException
     */
    public static String getJSONStringNotShared(JSONObject json, String name) throws JSONException {
        return new String(json.getString(name));
    }
    /**
     * JSONObjectからStringを取得する。char[]は必要以上に共有しない
     * @param json
     * @param name
     * @return
     */
    public static String optJSONStringNotShared(JSONObject json, String name) {
        return new String(json.optString(name));
    }
    /**
     * JSONObjectからStringを取得する。char[]は必要以上に共有しない
     * @param json
     * @param name
     * @return
     */
    public static String optJSONStringNotShared(JSONObject json, String name, String fallback) {
        String s = json.optString(name, fallback);
        if (s == null) {
            return s;
        } else {
            return new String(s);
        }
    }

    public static LayoutInflater getInflaterForDialog(Context context) {
        return LayoutInflater.from(
                new ContextThemeWrapper(context, R.style.Theme_Dialog));
    }
    public static LayoutInflater getInflaterForDialogDark(Context context) {
        return LayoutInflater.from(
                new ContextThemeWrapper(context, R.style.Theme_Dialog_Dark));
    }

    /**
     * Stringをlongに変換する
     * @param value
     * @param defaultValue parseに失敗した場合の戻り値
     * @return
     */
    public static long parseLong(String value, long defaultValue) {
        if (value == null) {
            return defaultValue;
        }
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    /**
     * Stringをintに変換する
     * @param value
     * @param defaultValue parseに失敗した場合の戻り値
     * @return
     */
    public static int parseInt(String value, int defaultValue) {
        if (value == null) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public static void gcStrong() {
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();
        runtime.runFinalization();
        runtime.gc();
    }

    public static void abortHttpUriRequest(final HttpUriRequest request) {
        if (request != null && !request.isAborted()) {
            if (ThreadUtil.isInMainThread()) {
                new Thread() {
                    @Override
                    public void run() {
                        request.abort();
                    }
                }.start();
            } else {
                request.abort();
            }
        }
    }

    /**
     * Context.MODE_MULTI_PROCESS付きのDefault SharedPreferencesを取得する
     * @see android.preference.PreferenceManager#getDefaultSharedPreferences
     * @param context
     * @return
     */
    public static SharedPreferences getDefaultSharedPreferencesMultiProcess(
            Context context) {
        return context.getSharedPreferences(context.getPackageName() + "_preferences",
                Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
    }

    public static PackageInfo getFlashPlayerInfo(Context context) {
        try {
            return context.getPackageManager().getPackageInfo(
                    "com.adobe.flashplayer", 0);
        } catch (NameNotFoundException e) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, e.toString());
            }
            return null;
        }
    }

    public static boolean isServiceRunning(Context context, String serviceClassName) {
        ActivityManager am = (ActivityManager) context.getSystemService(
                Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> list = am.getRunningServices(
                Integer.MAX_VALUE);
        if (list == null) {
            return false;
        }
        for (ActivityManager.RunningServiceInfo info : list) {
            if (info != null && info.service != null
                    && serviceClassName.equals(info.service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * WIFI_MODE_FULL_HIGH_PERF があればそれで、なければ WIFI_MODE_FULL で
     * {@link WifiManager.WifiLock} を作成する
     * @see WifiManager#createWifiLock(int, String)
     * @param context
     * @param tag
     * @return 失敗時はnull
     */
    public static WifiManager.WifiLock createWifiLockHigh(Context context,
            String tag) {
        WifiManager wm = (WifiManager) context.getSystemService(
                Context.WIFI_SERVICE);
        if (wm == null) {
            return null;
        }

        if (sWifiModeFullHigh == 0) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
                sWifiModeFullHigh = WifiManager.WIFI_MODE_FULL_HIGH_PERF;
            } else {
                try {
                    Field field = WifiManager.class.getField(
                            "WIFI_MODE_FULL_HIGH_PERF");
                    sWifiModeFullHigh = (Integer) field.get(null);
                } catch (Exception e) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "WIFI_MODE_FULL_HIGH_PERF not found", e);
                    }
                    sWifiModeFullHigh = WifiManager.WIFI_MODE_FULL;
                }
            }
        }
        return wm.createWifiLock(sWifiModeFullHigh, tag);
    }

    /**
     * {@link Serializable} をGZIPで圧縮したbyte[]を返す
     * @param s
     * @return エラー時はnull
     */
    public static byte[] deflateSerializable(Serializable s) {
        if (s == null) {
            return null;
        }

        OutputStream out = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            out = baos;
            GZIPOutputStream gzos = new GZIPOutputStream(baos);
            out = gzos;
            ObjectOutputStream oos = new ObjectOutputStream(gzos);
            out = oos;

            oos.writeObject(s);
            gzos.finish();
            return baos.toByteArray();
        } catch (IOException e) {
            Log.e(LOG_TAG, e.toString(), e);
        } finally {
            if (out != null) {
                FileUtil.closeIgnoreException(out);
            }
        }
        return null;
    }

    /**
     * GZIPで圧縮されたbyte[]を解凍して {@link Serializable} を返す
     * @param <T>
     * @param b
     * @return エラー時はnull
     */
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T inflateSerializable(byte[] b) {
        if (b == null) {
            return null;
        }

        InputStream in = null;
        try {
            GZIPInputStream gzis = new GZIPInputStream(new ByteArrayInputStream(b));
            in = gzis;
            ObjectInputStream ois = new ObjectInputStream(gzis);
            in = ois;
            return (T) ois.readObject();
        } catch (IOException e) {
            Log.e(LOG_TAG, e.toString(), e);
        } catch (ClassNotFoundException e) {
            Log.e(LOG_TAG, e.toString(), e);
        } finally {
            if (in != null) {
                FileUtil.closeIgnoreException(in);
            }
        }
        return null;
    }
}
