package jp.sourceforge.nicoro;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
//import android.support.v4.app.LoaderManager;
//import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;

import jp.gr.java_conf.shiseissi.commonlib.CacheReference;
import jp.gr.java_conf.shiseissi.commonlib.ViewUtil;
import jp.sourceforge.nicoro.StaticRes.integer;

public class MylistFragment extends ListFragment
implements
//LoaderManager.LoaderCallbacks<ChangeReference<JSONObject>>,
AdapterView.OnItemSelectedListener, Handler.Callback {
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;

    private static final int MSG_ID_LOAD_OCCURED_ERROR = 0;
    private static final int MSG_ID_CHANGE_LIST = 1;
    private static final int MSG_ID_ADAPTER_ON_STOP = 2;

//    private static final String KEY_GROUP_ID = "group_id";

    private static final String KEY_STATE_MYLIST_GROUP = "MylistGroup";

    private static final String KEY_LAST_GROUP_ID = "LAST_GROUP_ID";

    private static final String KEY_STATE_MYLIST_ADAPTER = "STATE_MYLIST_ADAPTER";

    private Context mContext;
//    private SharedPreferences mSharedPreferences;
    private LayoutInflater mLayoutInflater;

    private MylistListAdapter mAdapter;

    private ListEmptyProgressManager mEmptyProgress;
    /** マイリスト選択 */
    private Spinner mSpinnerMylist;
    /** マイリストのソート方法選択 */
    private Spinner mSpinnerSort;

    private int mLastSelection = -1;

    private String mLastGroupId;

    private StateManager mStateManager = new StateManager();

    private JSONObject mMylistGroup;

    private ArrayList<Comparator<JSONObject>> mMylistComparators;

    private NicoJSONCustomLoader mMylistGroupLoader;
    private NicoJSONCustomLoader mMylistLoader;
    /**
     * マイリストグループの読み込み
     */
    private class MylistGroupLoader extends NicoJSONCustomLoader {
        public MylistGroupLoader() {
            super(mContext);
        }

        @Override
        protected void onPostExecute(ChangeReference<JSONObject> result) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                        .append(": onPostExecute").toString());
            }
            if (result == null) {
                // 読み込みエラー
                mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
            } else {
                JSONObject json = result.strengthen();
                assert json != null;
                // 取り出したら元の値は弱参照に切り替え
                result.weaken();
                if (isRemoving() || isDetached() || mStateManager.wasDestroyed()) {
                    // removeされていたら表示には反映しない
                    return;
                }
                setMylistGroup(json, false);
            }
        }
    }
    /**
     * マイリスト本体の読み込み
     */
    private class MylistLoader extends NicoJSONCustomLoader {
        public MylistLoader() {
            super(mContext);
        }

        @Override
        protected void onPostExecute(ChangeReference<JSONObject> result) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                        .append(": onPostExecute").toString());
            }
            if (result == null) {
                // 読み込みエラー
                mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
            } else {
                JSONObject json = result.strengthen();
                assert json != null;
                // 取り出したら元の値は弱参照に切り替え
                result.weaken();
                if (isRemoving()) {
                    // removeされていたら表示には反映しない
                    return;
                }
                setMylist(json);
            }
        }
    }

    private String mResStringInfoCountPlay;
    private String mResStringInfoCountComment;
    private String mResStringInfoCountMylist;
    private String mResStringUpload;

    private final HandlerWrapper mHandler = new HandlerWrapper(this);

    @Override
    public boolean handleMessage(Message msg) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": handleMessage msg=").append(msg.toString())
                    .toString());
        }
        if (isRemoving()) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Fragment is removing. ignore message=")
                        .append(msg.toString()).toString());
            }
            return true;
        }
        if (mStateManager.wasDestroyed()) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Fragment was destroyed. ignore message=")
                        .append(msg.toString()).toString());
            }
            return true;
        }
        switch (msg.what) {
            case MSG_ID_LOAD_OCCURED_ERROR:
                mAdapter.setItems(null);
                mAdapter.notifyDataSetChanged();
                mEmptyProgress.setEmptyText(R.string.empty_mylist_fragment_error);
                mEmptyProgress.showEmptyText();
                // TODO エラーダイアログどうするか
                break;
            case MSG_ID_CHANGE_LIST:
                // 重複イベントは削除
                mHandler.removeMessages(MSG_ID_CHANGE_LIST);

                selectMylistGroup(mSpinnerMylist.getSelectedItemPosition());
                break;
            case MSG_ID_ADAPTER_ON_STOP:
                // XXX 非表示なのにGC後のリスト操作が入るとIllegalStateExceptionの可能性
                mAdapter.onStop();
                break;

            default:
                assert false : msg.what;
                break;
        }
        return true;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onCreate").toString());
        }
        super.onCreate(savedInstanceState);

        mStateManager.onCreate(savedInstanceState);

        mContext = getActivity().getApplicationContext();
//        mSharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(mContext);

        Resources res = mContext.getResources();
        mResStringInfoCountPlay = res.getString(R.string.info_count_play);
        mResStringInfoCountComment = res.getString(R.string.info_count_comment);
        mResStringInfoCountMylist = res.getString(R.string.info_count_mylist);
        mResStringUpload = res.getString(R.string.upload);

        setMylistComparators();

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onCreateView").toString());
        }
        mLayoutInflater = inflater;
        View v = inflater.inflate(R.layout.mylist_list, container, false);
        mEmptyProgress = new ListEmptyProgressManager(v);
        mEmptyProgress.setEmptyText(R.string.empty_mylist_fragment_pre);
        mEmptyProgress.showEmptyText();

        mSpinnerMylist = ViewUtil.findViewById(v, R.id.spinner_mylist);
        mSpinnerMylist.setOnItemSelectedListener(this);

        mSpinnerSort = ViewUtil.findViewById(v, R.id.spinner_sort);
        Activity activity = getActivity();
        ArrayAdapter<?> adapter = ArrayAdapter.createFromResource(activity,
                R.array.mylist_sort, R.layout.mylist_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinnerSort.setAdapter(adapter);
        mSpinnerSort.setOnItemSelectedListener(this);

        return v;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onActivityCreated").toString());
        }
        super.onActivityCreated(savedInstanceState);

        ListView listView = getListView();
        mAdapter = new MylistListAdapter();
        if (savedInstanceState != null) {
            if (mAdapter.onRestoreInstanceState(savedInstanceState)) {
                mLastGroupId = savedInstanceState.getString(KEY_LAST_GROUP_ID);
                mEmptyProgress.showEmptyText();
            }

            byte[] gzipBytes = savedInstanceState.getByteArray(
                    KEY_STATE_MYLIST_GROUP);
            String mylistGroup = Util.inflateSerializable(gzipBytes);
            if (mylistGroup != null) {
                try {
                    setMylistGroup(new JSONObject(mylistGroup), true);
                } catch (JSONException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
            }
        }
        setListAdapter(mAdapter);
        // ListViewの選択位置は、ListView自身のrestoreに任せる
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                Activity a = getActivity();
                if (a instanceof WebBrowserFragmentStarter) {
                    WebBrowserFragmentStarter starter =
                        (WebBrowserFragmentStarter) a;
                    Object obj = mAdapter.getItem(position);
                    if (obj instanceof JSONObject) {
                        JSONObject item = (JSONObject) obj;
                        try {
                            JSONObject itemData = item.getJSONObject("item_data");
                            String watchId = itemData.getString("watch_id");
                            String url = "http://www.nicovideo.jp/watch/" + watchId;

                            starter.startWebBrowserFragment(url);
                        } catch (JSONException e) {
                            Log.e(LOG_TAG, e.toString(), e);
                        }
                    }
                }
            }
        });

        if (mMylistGroup == null) {
//            getLoaderManager().initLoader(R.id.loader_mylistgroup, null, this);
            assert mMylistGroupLoader == null;
            mMylistGroupLoader = new MylistGroupLoader();
            mMylistGroupLoader.executeWrapper("www.nicovideo.jp",
                    "/api/mylistgroup/list");
        }
    }

    @Override
    public void onStart() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onStart").toString());
        }
        super.onStart();
        mStateManager.onStart();
        mHandler.removeMessages(MSG_ID_ADAPTER_ON_STOP);
        mAdapter.onStart();
    }

    @Override
    public void onResume() {
        super.onResume();
        mStateManager.onResume();
    }

    @Override
    public void onPause() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onPause").toString());
        }
        super.onPause();
        mStateManager.onPause();
    }

    @Override
    public void onStop() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onStop").toString());
        }
        super.onStop();
        mStateManager.onStop();
        // TODO nullクリアしないなら、明示的なリロードボタンか何かが欲しい
//        mLastGroupId = null;
        // リスト操作割り込みを避けるため少し遅らせて実行
        mHandler.sendEmptyMessageDelayed(MSG_ID_ADAPTER_ON_STOP, 500);
    }

    @Override
    public void onDestroy() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onDestroy").toString());
        }
        super.onDestroy();
        mStateManager.onDestroy();
        mAdapter.setItems(null);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onSaveInstanceState").toString());
        }
        super.onSaveInstanceState(outState);
        mStateManager.onSaveInstanceState();

        if (mMylistGroup != null) {
            // サイズが大きい懸念があるので圧縮
            byte[] gzipBytes = Util.deflateSerializable(mMylistGroup.toString());
            outState.putByteArray(KEY_STATE_MYLIST_GROUP, gzipBytes);
        }
        outState.putString(KEY_LAST_GROUP_ID, mLastGroupId);
        mAdapter.onSaveInstanceState(outState);
    }

    @Override
    public void onLowMemory() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append(": onLowMemory").toString());
        }
        super.onLowMemory();

        // TODO 色々とメモリ足りないっぽい
        // JSONの消費メモリ大またはLoaderがメモリリーク？
        // 500件のJSONArrayは結構痛いっぽい

        // TODO 下手にdestroyすると逆効果？
//        if (mMylistGroup != null) {
//            getLoaderManager().destroyLoader(R.id.loader_mylistgroup);
//        }
//        if (mAdapter.getCount() != 0) {
//            getLoaderManager().destroyLoader(R.id.loader_mylist);
//        }
    }

    private static class OptionsMenuItem {
        MenuItem listMenuPlaylist;
    }
    private OptionsMenuItem mOptionsMenuItem;

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.mylist_menu, menu);
        if (mOptionsMenuItem == null) {
            mOptionsMenuItem = new OptionsMenuItem();
        }
        mOptionsMenuItem.listMenuPlaylist = menu.findItem(R.id.menu_listmenu_playlist);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        if (mMylistGroup == null) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, "mylistgroup has not been loaded yet");
            }
            mOptionsMenuItem.listMenuPlaylist.setEnabled(false);
        } else {
            try {
                JSONArray group = mMylistGroup.getJSONArray("mylistgroup");
                int position = mSpinnerMylist.getSelectedItemPosition();
                int length = group.length();
                if (position >= length) {
                    // おそらくとりあえずマイリスト
                    mOptionsMenuItem.listMenuPlaylist.setEnabled(false);
                } else {
                    mOptionsMenuItem.listMenuPlaylist.setEnabled(true);
                }
            } catch (JSONException e) {
                Log.w(LOG_TAG, e.toString(), e);
                mOptionsMenuItem.listMenuPlaylist.setEnabled(false);
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_listmenu_playlist:
                startPlaylist();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void startPlaylist() {
        try {
            JSONArray group = mMylistGroup.getJSONArray("mylistgroup");
            int position = mSpinnerMylist.getSelectedItemPosition();
            int length = group.length();
            if (position >= length) {
                // TOOD おそらくとりあえずマイリスト。エラー表示
                return;
            }
            JSONObject mylist = group.getJSONObject(position);
            String mylistNumber = Util.getJSONStringNotShared(mylist, "id");
            if (mylistNumber == null) {
                Util.showErrorToast(mContext, R.string.errormessage_play_fail_unknown);
                return;
            }

            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            Fragment prev = fm.findFragmentByTag("playlist_dialog");
            if (prev != null) {
                ft.remove(prev);
            }
            ft.addToBackStack(null);

            StartPlaylistDialogFragment fragment =
                StartPlaylistDialogFragment.createInstance(mylistNumber);
            fragment.show(ft, "playlist_dialog");
        } catch (JSONException e) {
            Log.w(LOG_TAG, e.toString(), e);
            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
        }
    }

    private void setMylistGroup(JSONObject json, boolean wasRestored) {
        if (json == null) {
            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
            return;
        }
        try {
            JSONArray group = json.getJSONArray("mylistgroup");
            int length = group.length();
            ArrayList<String> arrayName = new ArrayList<String>(length + 1);
            for (int i = 0; i < length; ++i) {
                JSONObject mylist = group.getJSONObject(i);
                // nameにHTML文字（例：&lt;）が含まれる場合がある
                String name = Util.getJSONStringNotShared(mylist, "name");
                name = Util.convertHtmlEscapedCharacter(name);
                arrayName.add(name);
            }
            // とりあえずマイリスト
            arrayName.add(getString(R.string.deflist));

            mMylistGroup = json;
            Activity activity = getActivity();
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,
                    R.layout.mylist_spinner_item, arrayName);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            mSpinnerMylist.setAdapter(adapter);
            if (!wasRestored) {
                // とりあえずマイリストを最初に選択
                mSpinnerMylist.setSelection(arrayName.size() - 1);
            }

            ActivityCompat.invalidateOptionsMenu(activity);
        } catch (JSONException e) {
            Log.e(LOG_TAG, e.toString(), e);
            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
        }
    }

    void selectMylistGroup(int position) {
        if (mMylistGroup == null) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, "mylistgroup has not been loaded yet");
            }
            mAdapter.setItems(null);
            mAdapter.notifyDataSetChanged();
            return;
        }
        try {
            JSONArray group = mMylistGroup.getJSONArray("mylistgroup");
            int length = group.length();
            String groupId;
            if (position >= length) {
                // とりあえずマイリスト
                groupId = "";
            } else {
                JSONObject mylist = group.getJSONObject(position);
                groupId = Util.getJSONStringNotShared(mylist, "id");
            }
            if (TextUtils.equals(mLastGroupId, groupId)) {
                // 前回と同じ場合は読み込み省略
                return;
            }
            mLastGroupId = groupId;
//            Bundle args;
//            if (groupId.length() == 0) {
//                args = null;
//            } else {
//                args = new Bundle(1);
//                args.putString(KEY_GROUP_ID, groupId);
//            }
//            getLoaderManager().restartLoader(R.id.loader_mylist, args, this);
            if (mMylistLoader != null) {
                mMylistLoader.cancel(true);
            }
            mMylistLoader = new MylistLoader();
            String uri;
            if (groupId.length() == 0) {
                // とりあえずマイリスト
                uri = "/api/deflist/list";
            } else {
                uri = "/api/mylist/list?group_id=" + groupId;
            }
            mMylistLoader.executeWrapper("www.nicovideo.jp", uri);

            mEmptyProgress.showEmptyProgress();
            mAdapter.setItems(null);
            mAdapter.notifyDataSetChanged();
        } catch (JSONException e) {
            Log.e(LOG_TAG, e.toString(), e);
            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
        }
    }

    private void setMylist(JSONObject json) {
        if (json == null) {
            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
            return;
        }
        mEmptyProgress.setEmptyText(R.string.empty_mylist_fragment_pre);
        mEmptyProgress.showEmptyText();
        if (!mStateManager.wasDestroyed()) {
            mAdapter.setItems(json);
            mAdapter.notifyDataSetChanged();
            if (mLastSelection >= 0 && mLastSelection < mAdapter.getCount()) {
                getListView().setSelection(mLastSelection);
            }
            mLastSelection = -1;
        }
    }

    private void setMylistComparators() {
        int sortSize = integer.mylist_sort_short_play_time + 1;
        mMylistComparators = new ArrayList<Comparator<JSONObject>>(sortSize);
        for (int i = 0; i < sortSize; ++i) {
            mMylistComparators.add(null);
        }
        mMylistComparators.set(integer.mylist_sort_new_create,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareOldCreate(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_old_create,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareOldCreate(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_title_ascending,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareTitleAscending(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_title_descending,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareTitleAscending(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_mylist_comment_ascending,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareMylistCommentAscending(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_mylist_comment_descending,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareMylistCommentAscending(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_new_upload,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareOldUpload(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_old_upload,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareOldUpload(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_many_views,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareFewViews(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_few_views,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareFewViews(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_new_comment,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareOldComment(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_old_comment,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareOldComment(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_many_comments,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareFewComments(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_few_comments,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareFewComments(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_many_my_lists,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareFewMylists(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_few_my_lists,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareFewMylists(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_long_play_time,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return -compareShortPlayTime(object1, object2);
            }
        });
        mMylistComparators.set(integer.mylist_sort_short_play_time,
                new Comparator<JSONObject>() {
            @Override
            public int compare(JSONObject object1, JSONObject object2) {
                return compareShortPlayTime(object1, object2);
            }
        });
    }

    // 基本compareメソッドはすべて昇順

    static int compareLongValue(JSONObject object1, JSONObject object2, String key) {
        if (object1 == null) {
            if (object2 == null) {
                return 0;
            } else {
                return 1;
            }
        } else if (object2 == null) {
            return -1;
        }
        long val1 = object1.optLong(key);
        long val2 = object2.optLong(key);
        if (val1 < val2) {
            return -1;
        } else if (val1 == val2) {
            return 0;
        } else {
            return 1;
        }
    }
    static int compareStringValue(JSONObject object1, JSONObject object2, String key) {
        if (object1 == null) {
            if (object2 == null) {
                return 0;
            } else {
                return 1;
            }
        } else if (object2 == null) {
            return -1;
        }
        String val1 = object1.optString(key);
        String val2 = object2.optString(key);
        return val1.compareTo(val2);
    }
    static int compareItemDataLongValue(JSONObject object1, JSONObject object2, String key) {
        if (object1 == null) {
            if (object2 == null) {
                return 0;
            } else {
                return 1;
            }
        } else if (object2 == null) {
            return -1;
        }
        JSONObject data1 = object1.optJSONObject("item_data");
        JSONObject data2 = object2.optJSONObject("item_data");
        return compareLongValue(data1, data2, key);
    }
    static int compareItemDataStringValue(JSONObject object1, JSONObject object2, String key) {
        if (object1 == null) {
            if (object2 == null) {
                return 0;
            } else {
                return 1;
            }
        } else if (object2 == null) {
            return -1;
        }
        JSONObject data1 = object1.optJSONObject("item_data");
        JSONObject data2 = object2.optJSONObject("item_data");
        return compareStringValue(data1, data2, key);
    }

    static int compareOldCreate(JSONObject object1, JSONObject object2) {
        return compareLongValue(object1, object2, "create_time");
    }
    static int compareTitleAscending(JSONObject object1, JSONObject object2) {
        return compareItemDataStringValue(object1, object2, "title");
    }
    static int compareMylistCommentAscending(JSONObject object1, JSONObject object2) {
        return compareStringValue(object1, object2, "description");
    }
    static int compareOldUpload(JSONObject object1, JSONObject object2) {
        return compareItemDataLongValue(object1, object2, "first_retrieve");
    }
    static int compareFewViews(JSONObject object1, JSONObject object2) {
        return compareItemDataLongValue(object1, object2, "view_counter");
    }
    static int compareOldComment(JSONObject object1, JSONObject object2) {
        // TODO コメントの古い・新しいがこのキーなのか自信ない
        // というかそもそもJSONデータの中にあるかどうかも不明
        return compareItemDataLongValue(object1, object2, "update_time");
    }
    static int compareFewComments(JSONObject object1, JSONObject object2) {
        return compareItemDataLongValue(object1, object2, "num_res");
    }
    static int compareFewMylists(JSONObject object1, JSONObject object2) {
        return compareItemDataLongValue(object1, object2, "mylist_counter");
    }
    static int compareShortPlayTime(JSONObject object1, JSONObject object2) {
        return compareItemDataLongValue(object1, object2, "length_seconds");
    }

    private class MylistListAdapter extends BaseAdapter {
        private CacheReference<ArrayList<JSONObject>> mItems;
        private Comparator<JSONObject> mComparator;
        private final DateFormat mDateFormat = DateFormat.getDateTimeInstance(
                DateFormat.LONG, DateFormat.DEFAULT);
        private final ThumbnailCacher mThumbnailCacher =
            NicoroApplication.getInstance(getActivity())
                .getThumbnailCacher();

        @Override
        public int getCount() {
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (items == null) {
                return 0;
            } else {
                return items.size();
            }
        }

        @Override
        public Object getItem(int position) {
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (items == null || items.size() == 0) {
                return null;
            } else {
                return items.get(position);
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;
            ListItem listItem;
            if (convertView == null) {
                LayoutInflater inflater = mLayoutInflater;
                view = inflater.inflate(R.layout.mylist, parent, false);
                listItem = new ListItem();
                view.setTag(listItem);
                listItem.thumbnail = ViewUtil.findViewById(view, R.id.thumbnail);
                listItem.time = ViewUtil.findViewById(view, R.id.time);
                listItem.title = ViewUtil.findViewById(view, R.id.title);
                listItem.view = ViewUtil.findViewById(view, R.id.view);
                listItem.comment = ViewUtil.findViewById(view, R.id.comment);
                listItem.mylist = ViewUtil.findViewById(view, R.id.mylist);
                listItem.length = ViewUtil.findViewById(view, R.id.length);
            } else {
                view = convertView;
                listItem = (ListItem) convertView.getTag();
            }

            String thumbnail = null;
            String title = null;
            String viewCounter = null;
            String mylist = null;
            String comment = null;
            int lengthSeconds = 0;
            long firstRetrive = 0;

            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (items != null) {
                JSONObject item = items.get(position);
                JSONObject itemData = item.optJSONObject("item_data");
                if (itemData != null) {
                    thumbnail = Util.optJSONStringNotShared(itemData, "thumbnail_url", null);
                    title = Util.optJSONStringNotShared(itemData, "title", null);
                    title = Util.convertHtmlEscapedCharacter(title);
                    viewCounter = Util.optJSONStringNotShared(itemData, "view_counter", null);
                    mylist = Util.optJSONStringNotShared(itemData, "mylist_counter", null);
                    comment = Util.optJSONStringNotShared(itemData, "num_res", null);
                    comment = Util.convertHtmlEscapedCharacter(comment);
                    lengthSeconds = itemData.optInt("length_seconds");
                    firstRetrive = itemData.optLong("first_retrieve");
                }
            }

            AsyncBitmapDrawable drawable;
            ViewGroup.LayoutParams params = listItem.thumbnail.getLayoutParams();
            if (thumbnail == null) {
                drawable = new AsyncBitmapDrawable(null,
                        params.width, params.height);
            } else {
                Bitmap bitmap = mThumbnailCacher.getThumbnail(thumbnail);
                if (bitmap == null) {
                    drawable = new AsyncBitmapDrawable(params.width, params.height, true);
                    mThumbnailCacher.loadThumbnail(thumbnail,
                            new CallbackMessage<Bitmap, Void>(
                                    drawable.getHandler(), 0));
                } else {
                    drawable = new AsyncBitmapDrawable(bitmap,
                            params.width, params.height, true);
                }
            }
            listItem.thumbnail.setImageDrawable(drawable);

            listItem.title.setText(title);

            listItem.view.getTextBuilderWithClear().append(mResStringInfoCountPlay)
                .append(viewCounter);
            listItem.view.notifyUpdateText();

            listItem.comment.getTextBuilderWithClear().append(mResStringInfoCountComment)
                .append(comment);
            listItem.comment.notifyUpdateText();

            listItem.mylist.getTextBuilderWithClear().append(mResStringInfoCountMylist)
                .append(mylist);
            listItem.mylist.notifyUpdateText();

            listItem.time.getTextBuilderWithClear().append(mDateFormat.format(
                    new Date(firstRetrive * 1000L)))
                    .append(" ").append(mResStringUpload);
            listItem.time.notifyUpdateText();

            Util.appendPlayTime(listItem.length.getTextBuilderWithClear(),
                    lengthSeconds / 60, lengthSeconds % 60);
            listItem.length.notifyUpdateText();

            return view;
        }

        public void setItems(JSONObject json) {
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (json == null) {
                if (items != null) {
                    items.clear();
                }
            } else {
                try {
                    JSONArray array = json.getJSONArray("mylistitem");
                    // sortのためArrayListに再配置
                    int length = array.length();
                    if (mItems == null) {
                        items = new ArrayList<JSONObject>(array.length());
                        mItems = new CacheReference<ArrayList<JSONObject>>(items);
                    } else if (items == null) {
                        items = new ArrayList<JSONObject>(array.length());
                        mItems.reset(items);
                    } else {
                        items.clear();
                        items.ensureCapacity(length);
                    }
                    for (int i = 0; i < length; ++i) {
                        JSONObject obj = array.optJSONObject(i);
                        if (obj != null) {
                            items.add(obj);
                        }
                    }

                    if (mComparator != null) {
                        // XXX ArrayStoreExceptionが発生することがある？原因不明
                        Collections.sort(items, mComparator);
                    }
                } catch (JSONException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    items = (mItems == null ? null : mItems.get());
                    if (items != null) {
                        items.clear();
                    }
                    mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
                }
            }
        }

        public void sort(Comparator<JSONObject> comparator) {
            mComparator = comparator;
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (items != null) {
                Collections.sort(items, comparator);
            }
        }

        public void onStart() {
            if (mItems != null) {
                ArrayList<JSONObject> items = mItems.strengthen();
                if (items == null) {
                    // 再取得
//                    mLastGroupId = null; // onItemSelectedとかぶる
                    mHandler.sendEmptyMessage(MSG_ID_CHANGE_LIST);
                    notifyDataSetChanged();
                }
            }
        }
        public void onStop() {
            if (mItems != null) {
                // TODO 自前のメモリ管理必要か否か
//                mItems.weaken();
            }
        }

        public void onSaveInstanceState(Bundle outState) {
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            if (items != null) {
                int size = items.size();
                String[] itemsString = new String[size];
                for (int i = 0; i < size; ++i) {
                    itemsString[i] = items.get(i).toString();
                }
                // サイズが大きい懸念があるので圧縮
                byte[] gzipBytes = Util.deflateSerializable(itemsString);
                outState.putByteArray(KEY_STATE_MYLIST_ADAPTER, gzipBytes);
            }
        }

        public boolean onRestoreInstanceState(Bundle savedInstanceState) {
            byte[] gzipBytes = savedInstanceState.getByteArray(
                    KEY_STATE_MYLIST_ADAPTER);
            String[] itemsString = Util.inflateSerializable(gzipBytes);
            if (itemsString == null) {
                return false;
            }
            int length = itemsString.length;
            if (length == 0) {
                return false;
            }
            ArrayList<JSONObject> items = (mItems == null ? null : mItems.get());
            try {
                if (mItems == null) {
                    items = new ArrayList<JSONObject>(length);
                    mItems = new CacheReference<ArrayList<JSONObject>>(items);
                } else if (items == null) {
                    items = new ArrayList<JSONObject>(length);
                    mItems.reset(items);
                } else {
                    items.clear();
                    items.ensureCapacity(length);
                }
                for (int i = 0; i < length; ++i) {
                    items.add(new JSONObject(itemsString[i]));
                }

                if (mComparator != null) {
                    Collections.sort(items, mComparator);
                }
                return true;
            } catch (JSONException e) {
                Log.e(LOG_TAG, e.toString(), e);
                items = (mItems == null ? null : mItems.get());
                if (items != null) {
                    items.clear();
                }
                mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
                return false;
            }
        }
    }

    private static class ListItem {
        public ImageView thumbnail;
        public VariableLabelView time;
        public VariableLabelView title;
        public VariableLabelView view;
        public VariableLabelView comment;
        public VariableLabelView mylist;
        public VariableLabelView length;
    }

//    @Override
//    public Loader<ChangeReference<JSONObject>> onCreateLoader(int id, Bundle args) {
//        if (DEBUG_LOGV) {
//            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
//                    .append(": onCreateLoader id=").append(id).toString());
//        }
//        switch (id) {
//            case R.id.loader_mylistgroup:
//                return new NicoJSONLoader(mContext, "www.nicovideo.jp",
//                        "/api/mylistgroup/list");
//            case R.id.loader_mylist:
//                String groupId = (args == null) ? null : args.getString(KEY_GROUP_ID);
//                if (groupId == null) {
//                    // とりあえずマイリスト
//                    return new NicoJSONLoader(mContext, "www.nicovideo.jp",
//                            "/api/deflist/list");
//                } else {
//                    return new NicoJSONLoader(mContext, "www.nicovideo.jp",
//                            "/api/mylist/list?group_id=" + groupId);
//                }
//            default:
//                assert false : id;
//                return null;
//        }
//    }

//    @Override
//    public void onLoadFinished(Loader<ChangeReference<JSONObject>> loader,
//            ChangeReference<JSONObject> data) {
//        int id = loader.getId();
//        if (DEBUG_LOGV) {
//            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
//                    .append(": onLoadFinished id=").append(id).toString());
//        }
//        if (data == null) {
//            // 読み込みエラー
//            mHandler.sendEmptyMessage(MSG_ID_LOAD_OCCURED_ERROR);
//        } else {
//            JSONObject json = data.strengthen();
//            assert json != null;
//            // 取り出したら元の値は弱参照に切り替え
//            data.weaken();
//            if (isRemoving()) {
//                // removeされていたら表示には反映しない
//                return;
//            }
//            switch (id) {
//                case R.id.loader_mylistgroup:
//                    setMylistGroup(json);
//                    break;
//                case R.id.loader_mylist:
//                    setMylist(json);
//                    break;
//                default:
//                    assert false : id;
//                    break;
//            }
//        }
//    }

//    @Override
//    public void onLoaderReset(Loader<ChangeReference<JSONObject>> loader) {
//        if (DEBUG_LOGV) {
//            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
//                    .append(": onLoaderReset").toString());
//        }
//    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (parent == mSpinnerMylist) {
//            selectMylistGroup(position);
            mHandler.sendEmptyMessage(MSG_ID_CHANGE_LIST);
        } else if (parent == mSpinnerSort) {
            mAdapter.sort(mMylistComparators.get(position));
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
}
