package jp.sourceforge.nicoro;

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

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
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.ListFragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
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 android.widget.SpinnerAdapter;

import java.util.ArrayList;

import jp.gr.java_conf.shiseissi.commonlib.ViewUtil;

public class RankingFragment extends ListFragment
implements AdapterView.OnItemSelectedListener, Handler.Callback {
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
//    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;

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

    private static final String KEY_LAST_URL = "LAST_URL";
    private static final String KEY_RANKING_LIST_ADAPTER = "RankingListAdapter";

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

    RssLoader mRssLoader;
    private RankingListAdapter mAdapter;

    private VariableLabelView mViewTitle;
    private ListEmptyProgressManager mEmptyProgress;
    /** ランキング カテゴリ */
    private Spinner mSpinnerCategory;
    /** ランキング 期間（毎時、デイリーなど） */
    private Spinner mSpinnerTerm;
    /** ランキング 対象（コメント、マイリストなど） */
    private Spinner mSpinnerTarget;

    private ArrayAdapter<?> mAdapterCategory;

    private String mLastUrl;

    private boolean mSavedInstanceState;

    private String mResStringUpload;
    private String mResStringView;
    private String mResStringRes;
    private String mResStringMylist;
    private String mResStringTotal;
    private String mResStringHourly;
    private String mResStringDaily;
    private String mResStringWeekly;
    private String mResStringMonthly;
    private String[] mResStringArrayCategoryUrl;
    private String[] mResStringArrayTermUrl;
    private String[] mResStringArrayTargetUrl;
    private int mResIntegerCategoryDefaultPosition;
    private int mResIntegerTermDefaultPosition;
    private int mResIntegerTargetDefaultPosition;

    private final HandlerWrapper mHandler = new HandlerWrapper(this);

    @Override
    public boolean handleMessage(Message msg) {
        if (isRemoving()) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Fragment is removing. ignore message=")
                        .append(msg.toString()).toString());
            }
            return true;
        }
        if (getActivity() == null) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Fragment was detached. ignore message=")
                        .append(msg.toString()).toString());
            }
            return true;
        }
        RssLoader result;
        switch (msg.what) {
            case MSG_ID_LOAD_FINISHED:
                result = (RssLoader) msg.obj;
                // 現在実行中のloaderからの通知のみ画面に反映
                if (result == mRssLoader) {
                    mEmptyProgress.showEmptyText();
                    mViewTitle.setText(mRssLoader.getTitle());
                    mAdapter.setItems(mRssLoader.getItems());
                    mAdapter.notifyDataSetChanged();
                }
                break;
            case MSG_ID_LOAD_OCCURED_ERROR:
                result = (RssLoader) msg.obj;
                // 現在実行中のloaderからのエラー通知のみ画面に反映
                if (result == mRssLoader) {
                    mEmptyProgress.showEmptyText();
                    if (!mSavedInstanceState) {
                        String errorMessage = result.getLastErrorMessage();
                        CloseDialogFragment.createErrorDialog(errorMessage,
                                false).show(getFragmentManager(),
                                        mContext.getString(R.string.tag_close_dialog_fragment));
                    }
                }
                break;
            case MSG_ID_CHANGE_LIST:
                // 重複イベントは削除
                mHandler.removeMessages(MSG_ID_CHANGE_LIST);

                if (createAndStartRssLoader()) {
                    mEmptyProgress.showEmptyProgress();
                    mViewTitle.setText("");
                    mAdapter.setItems(null);
                    mAdapter.notifyDataSetChanged();
                }
                break;
            default:
                assert false : msg.what;
                break;
        }
        return true;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

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

        mSavedInstanceState = false;

        Resources res = mContext.getResources();
        mResStringUpload = res.getString(R.string.upload);
        mResStringView = res.getString(R.string.view);
        mResStringRes = res.getString(R.string.res);
        mResStringMylist = res.getString(R.string.mylist);
        mResStringTotal = res.getString(R.string.total);
        mResStringHourly = res.getString(R.string.hourly);
        mResStringDaily = res.getString(R.string.daily);
        mResStringWeekly = res.getString(R.string.weekly);
        mResStringMonthly = res.getString(R.string.monthly);
        mResStringArrayCategoryUrl = res.getStringArray(R.array.ranking_category_url);
        mResStringArrayTermUrl = res.getStringArray(R.array.ranking_term_url);
        mResStringArrayTargetUrl = res.getStringArray(R.array.ranking_target_url);
        mResIntegerCategoryDefaultPosition = res.getInteger(
                R.integer.ranking_category_default_position);
        mResIntegerTermDefaultPosition = res.getInteger(
                R.integer.ranking_term_default_position);
        mResIntegerTargetDefaultPosition = res.getInteger(
                R.integer.ranking_target_default_position);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mLayoutInflater = inflater;
        View v = inflater.inflate(R.layout.ranking_list, container, false);
        mEmptyProgress = new ListEmptyProgressManager(v);
        mViewTitle = ViewUtil.findViewById(v, R.id.list_title);
        mEmptyProgress.showEmptyProgress();

        SharedPreferences sp = mSharedPreferences;
        int categoryDefault = sp.getInt(NicoroConfig.RANKING_CATEGORY, mResIntegerCategoryDefaultPosition);
        int termDefault = sp.getInt(NicoroConfig.RANKING_TERM, mResIntegerTermDefaultPosition);
        int targetDefault = sp.getInt(NicoroConfig.RANKING_TARGET, mResIntegerTargetDefaultPosition);

        ArrayAdapter<?> adapter;
        mSpinnerCategory = ViewUtil.findViewById(v, R.id.spinner_category);

        Activity activity = getActivity();
        adapter = ArrayAdapter.createFromResource(activity,
                R.array.ranking_category, R.layout.ranking_spinner_item);
        mAdapterCategory = adapter;
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        mSpinnerCategory.setAdapter(mAdapterCategory);

        mSpinnerTerm = ViewUtil.findViewById(v, R.id.spinner_term);
        adapter = ArrayAdapter.createFromResource(activity,
                R.array.ranking_term, R.layout.ranking_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinnerTerm.setAdapter(adapter);

        mSpinnerTarget = ViewUtil.findViewById(v, R.id.spinner_target);
        adapter = ArrayAdapter.createFromResource(activity,
                R.array.ranking_target, R.layout.ranking_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinnerTarget.setAdapter(adapter);

        mSpinnerCategory.setOnItemSelectedListener(this);
        mSpinnerTerm.setOnItemSelectedListener(this);
        mSpinnerTarget.setOnItemSelectedListener(this);

        if (savedInstanceState == null) {
            mSpinnerTerm.setSelection(termDefault);
            mSpinnerCategory.setSelection(categoryDefault);
            mSpinnerTarget.setSelection(targetDefault);
        }

        return v;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        ListView listView = getListView();
        mAdapter = new RankingListAdapter();
        if (savedInstanceState != null) {
            if (mAdapter.onRestoreInstanceState(savedInstanceState)) {
                // 有効なアイテムがあるときだけmLastUrl復帰
                mLastUrl = savedInstanceState.getString(KEY_LAST_URL);
                mEmptyProgress.showEmptyText();
            }
        }
        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;
                    RssLoader.Item item = (RssLoader.Item) mAdapter.getItem(position);
                    if (item != null) {
                        String url = item.link;
                        starter.startWebBrowserFragment(url);
                    }
                }
            }
        });
    }

    @Override
    public void onPause() {
        super.onPause();
        // TODO mLastUrlクリアしないなら、明示的なリロードボタンか何かが欲しい
//        mLastUrl = null;

        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putInt(NicoroConfig.RANKING_CATEGORY, mSpinnerCategory.getSelectedItemPosition());
        editor.putInt(NicoroConfig.RANKING_TERM, mSpinnerTerm.getSelectedItemPosition());
        editor.putInt(NicoroConfig.RANKING_TARGET, mSpinnerTarget.getSelectedItemPosition());
        editor.commit();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_LAST_URL, mLastUrl);
        mAdapter.onSaveInstanceState(outState);
        mSavedInstanceState = true;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        mHandler.sendEmptyMessage(MSG_ID_CHANGE_LIST);
    }

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

    private boolean createAndStartRssLoader() {
        assert mHandler != null;

        String category;
        int positionCategory = mSpinnerCategory.getSelectedItemPosition();
        SpinnerAdapter currentCategoryAdapter = mSpinnerCategory.getAdapter();
        assert currentCategoryAdapter == mAdapterCategory;
        category = mResStringArrayCategoryUrl[positionCategory];
        String term = mResStringArrayTermUrl[mSpinnerTerm.getSelectedItemPosition()];
        String target = mResStringArrayTargetUrl[mSpinnerTarget.getSelectedItemPosition()];
        String url = new StringBuilder("http://www.nicovideo.jp/ranking/")
            .append(target).append('/').append(term).append('/')
            .append(category).append("?rss=2.0").toString();

        if (TextUtils.equals(mLastUrl, url)) {
            // 前回と同じ場合は読み込み省略
            return false;
        }
        mLastUrl = url;

        // TODO ここでCookie取得すべきかどうか
        String userSession = NicoroConfig.getCookieUserSession(mSharedPreferences);
        RssLoader loader = new RssLoader(
                url,
                userSession);
        // TODO: ANR対策の為に***Loader類のabort対応が必要
        if (mRssLoader != null) {
            mRssLoader.finish();
        }
        mRssLoader = loader;
        CallbackMessage<RssLoader, RssLoader> callback = new CallbackMessage<RssLoader, RssLoader>(
                mHandler, MSG_ID_LOAD_FINISHED, MSG_ID_LOAD_OCCURED_ERROR);
        loader.registerCallback(callback);
        loader.startLoad();
        return true;
    }

    private class RankingListAdapter extends BaseAdapter {
        private ArrayList<RssLoader.Item> mItems;
        private ThumbnailCacher mThumbnailCacher =
            NicoroApplication.getInstance(getActivity())
                .getThumbnailCacher();

        @Override
        public int getCount() {
            if (mItems == null) {
                return 0;
            } else {
                return mItems.size();
            }
        }

        @Override
        public Object getItem(int position) {
            if (mItems == null) {
                return null;
            } else {
                return mItems.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.ranking, parent, false);
                listItem = new ListItem();
                view.setTag(listItem);
                listItem.thumbnail = ViewUtil.findViewById(view, R.id.thumbnail);
                listItem.title = ViewUtil.findViewById(view, R.id.title);
                listItem.description = ViewUtil.findViewById(view, R.id.description);
                listItem.info1 = ViewUtil.findViewById(view, R.id.info1);
                listItem.info2 = ViewUtil.findViewById(view, R.id.info2);
                listItem.info3 = ViewUtil.findViewById(view, R.id.info3);
                listItem.info4 = ViewUtil.findViewById(view, R.id.info4);
                listItem.info5 = ViewUtil.findViewById(view, R.id.info5);
                listItem.info6 = ViewUtil.findViewById(view, R.id.info6);
            } else {
                view = convertView;
                listItem = (ListItem) convertView.getTag();
            }
            RssLoader.Item item = mItems.get(position);

            AsyncBitmapDrawable drawable;
            ViewGroup.LayoutParams params = listItem.thumbnail.getLayoutParams();
            if (item.thumbnail == null) {
                drawable = new AsyncBitmapDrawable(null,
                        params.width, params.height);
            } else {
                Bitmap bitmap = mThumbnailCacher.getThumbnail(item.thumbnail);
                if (bitmap == null) {
                    drawable = new AsyncBitmapDrawable(params.width, params.height, true);
                    mThumbnailCacher.loadThumbnail(item.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(Util.convertHtmlEscapedCharacter(item.title));
            listItem.description.setText(Util.convertHtmlEscapedCharacter(item.description));

            StringBuilder builder = listItem.info1.getTextBuilderWithClear();
            builder.append(item.infoNumber)
                .append("pts.｜")
                .append(item.infoLength)
                .append("｜")
                .append(item.infoDate)
                .append(' ')
                .append(mResStringUpload);
            listItem.info1.notifyUpdateText();

            updateViewTotal(listItem.info2, item);
            updateViewHourly(listItem.info3, item);
            updateViewDaily(listItem.info4, item);
            updateViewWeekly(listItem.info5, item);
            updateViewMonthly(listItem.info6, item);

            return view;
        }

        private void updateViewTotal(VariableLabelView info, RssLoader.Item item) {
            if (item.infoTotalView == null) {
                info.setVisibility(View.GONE);
            } else {
                info.setVisibility(View.VISIBLE);
                StringBuilder builder = info.getTextBuilderWithClear();
                if (item.infoTotalRes == null || item.infoTotalMylist == null) {
                    Log.w(LOG_TAG, "info total is lacked");
                }
                builder.append(mResStringTotal)
                    .append(' ').append(mResStringView).append('：')
                    .append(item.infoTotalView)
                    .append(' ').append(mResStringRes).append('：')
                    .append(item.infoTotalRes)
                    .append(' ').append(mResStringMylist).append('：')
                    .append(item.infoTotalMylist);
                info.notifyUpdateText();
            }
        }
        private void updateViewHourly(VariableLabelView info, RssLoader.Item item) {
            if (item.infoHourlyView == null) {
                info.setVisibility(View.GONE);
            } else {
                info.setVisibility(View.VISIBLE);
                StringBuilder builder = info.getTextBuilderWithClear();
                if (item.infoHourlyRes == null || item.infoHourlyMylist == null) {
                    Log.w(LOG_TAG, "info hourly is lacked");
                }
                builder.append(mResStringHourly)
                    .append(' ').append(mResStringView).append('：')
                    .append(item.infoHourlyView)
                    .append(' ').append(mResStringRes).append('：')
                    .append(item.infoHourlyRes)
                    .append(' ').append(mResStringMylist).append('：')
                    .append(item.infoHourlyMylist);
                info.notifyUpdateText();
            }
        }
        private void updateViewDaily(VariableLabelView info, RssLoader.Item item) {
            if (item.infoDailyView == null) {
                info.setVisibility(View.GONE);
            } else {
                info.setVisibility(View.VISIBLE);
                StringBuilder builder = info.getTextBuilderWithClear();
                if (item.infoDailyRes == null || item.infoDailyMylist == null) {
                    Log.w(LOG_TAG, "info daily is lacked");
                }
                builder.append(mResStringDaily)
                    .append(' ').append(mResStringView).append('：')
                    .append(item.infoDailyView)
                    .append(' ').append(mResStringRes).append('：')
                    .append(item.infoDailyRes)
                    .append(' ').append(mResStringMylist).append('：')
                    .append(item.infoDailyMylist);
                info.notifyUpdateText();
            }
        }
        private void updateViewWeekly(VariableLabelView info, RssLoader.Item item) {
            if (item.infoWeeklyView == null) {
                info.setVisibility(View.GONE);
            } else {
                info.setVisibility(View.VISIBLE);
                StringBuilder builder = info.getTextBuilderWithClear();
                if (item.infoWeeklyRes == null || item.infoWeeklyMylist == null) {
                    Log.w(LOG_TAG, "info weekly is lacked");
                }
                builder.append(mResStringWeekly)
                    .append(' ').append(mResStringView).append('：')
                    .append(item.infoWeeklyView)
                    .append(' ').append(mResStringRes).append('：')
                    .append(item.infoWeeklyRes)
                    .append(' ').append(mResStringMylist).append('：')
                    .append(item.infoWeeklyMylist);
                info.notifyUpdateText();
            }
        }
        private void updateViewMonthly(VariableLabelView info, RssLoader.Item item) {
            if (item.infoMonthlyView == null) {
                info.setVisibility(View.GONE);
            } else {
                info.setVisibility(View.VISIBLE);
                StringBuilder builder = info.getTextBuilderWithClear();
                if (item.infoMonthlyRes == null || item.infoMonthlyMylist == null) {
                    Log.w(LOG_TAG, "info monthly is lacked");
                }
                builder.append(mResStringMonthly)
                    .append(' ').append(mResStringView).append('：')
                    .append(item.infoMonthlyView)
                    .append(' ').append(mResStringRes).append('：')
                    .append(item.infoMonthlyRes)
                    .append(' ').append(mResStringMylist).append('：')
                    .append(item.infoMonthlyMylist);
                info.notifyUpdateText();
            }
        }

        public void setItems(ArrayList<RssLoader.Item> items) {
            if (items == null) {
                if (mItems != null) {
                    mItems.clear();
                }
            } else {
                if (mItems == null) {
                    mItems = new ArrayList<RssLoader.Item>(items);
                } else {
                    mItems.clear();
                    mItems.addAll(items);
                }
            }
        }

        public void onSaveInstanceState(Bundle outState) {
            // FIXME ViewPagerのFragment復帰でどうしてもクラッシュすることがあるので、いったん実装を変える
//            outState.putParcelableArrayList(KEY_RANKING_LIST_ADAPTER, mItems);
            // サイズが大きい懸念があるので圧縮
            byte[] gzipBytes = Util.deflateSerializable(mItems);
            outState.putByteArray(KEY_RANKING_LIST_ADAPTER, gzipBytes);
        }

        public boolean onRestoreInstanceState(Bundle savedInstanceState) {
            // FIXME ViewPagerのFragment復帰でどうしてもクラッシュすることがあるので、いったん実装を変える
//            ArrayList<RssLoader.Item> items =
//                savedInstanceState.getParcelableArrayList(KEY_RANKING_LIST_ADAPTER);
            byte[] gzipBytes = savedInstanceState.getByteArray(
                    KEY_RANKING_LIST_ADAPTER);
            ArrayList<RssLoader.Item> items = Util.inflateSerializable(gzipBytes);
            if (items == null || items.size() == 0) {
                return false;
            } else {
                setItems(items);
                return true;
            }
        }
    }

    private static class ListItem {
        public ImageView thumbnail;
        public VariableLabelView title;
        public VariableLabelView description;
        public VariableLabelView info1;
        public VariableLabelView info2;
        public VariableLabelView info3;
        public VariableLabelView info4;
        public VariableLabelView info5;
        public VariableLabelView info6;
    }
}
