package jp.sourceforge.nicoro;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.support.v4.app.ListFragment;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;

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

public class AccessHistoryFragment extends ListFragment
implements Handler.Callback {
    private static final int MSG_ID_THUMBINFO_FINISHED = 0;
    private static final int MSG_ID_THUMBINFO_OCCURRED_ERROR = 1;
    private static final int MSG_ID_UPDATE_CACHE_DATA = 2;

    private static final String KEY_CACHE_DATA = "CACHE_DATA";

    private static final String TAG_VIDEO_CACHE_SERVICE = "AccessHistoryFragment";

    private AccessHistoryListAdapter mAdapter;

    private ListEmptyProgressManager mEmptyProgress;
    private TextView mInfoCacheFile;

    private StateManager mStateManager = new StateManager();

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

    /**
     * ContextMenuを表示してから選択するまでにAdapterの内容が変わると嫌なのでメンバで保存
     */
    private VideoLoader.ExternalInfoData mInfoDataForContextMenu;

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

    private VideoCacheServiceConnection mVideoCacheServiceConnection;

    private class VideoCacheServiceConnectionCallback implements VideoCacheServiceConnection.Callback {

        @Override
        public void onNotifyProgress(String videoV, int ecoType, int seekOffsetWrite,
                int contentLength) throws RemoteException {
        }

        @Override
        public void onStart(String videoV, int ecoType) throws RemoteException {
            // FIXME タイミングが早すぎるので遅らせてみたがまだ間に合わない
            mHandler.sendEmptyMessageDelayed(MSG_ID_UPDATE_CACHE_DATA, 1000);
        }

        @Override
        public void onFinished(String videoV, int ecoType) throws RemoteException {
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_CACHE_DATA);
        }

        @Override
        public void onAllFinished() throws RemoteException {
        }

        @Override
        public void onDeleted(String videoV) throws RemoteException {
            // 表示更新
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_CACHE_DATA);
        }

        @Override
        public void onAllDeleted() throws RemoteException {
            // 表示更新
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_CACHE_DATA);
        }
    }
    private final VideoCacheServiceConnectionCallback mVideoCacheServiceCallback = new VideoCacheServiceConnectionCallback();

    private class UpdateCacheDataTask extends AsyncTask<Void, Void, ArrayList<VideoLoader.ExternalInfoData>> {
        @Override
        protected ArrayList<VideoLoader.ExternalInfoData> doInBackground(Void... params) {
            ArrayList<VideoLoader.ExternalInfoData> cacheData =
                VideoLoader.getCacheExternalInfoData(mContext);
            return cacheData;
        }

        @Override
        protected void onPostExecute(ArrayList<VideoLoader.ExternalInfoData> result) {
            mEmptyProgress.showEmptyText();
            if (!mStateManager.wasDestroyed()) {
                mAdapter.setCacheData(result);
                mAdapter.notifyDataSetChanged();
            }
        }
    };

    private final HandlerWrapper mHandler = new HandlerWrapper(this);

    @Override
    public boolean handleMessage(Message msg) {
        if (mStateManager.wasDestroyed()) {
            return true;
        }
        switch (msg.what) {
            case MSG_ID_THUMBINFO_FINISHED:
                ThumbInfo thumbInfo = (ThumbInfo) msg.obj;
                ListView listView = getListView();
                int first = listView.getFirstVisiblePosition();
                int last = listView.getLastVisiblePosition();
                for (int i = first; i <= last; ++i) {
                    VideoLoader.ExternalInfoData data =
                        (VideoLoader.ExternalInfoData) mAdapter.getItem(i);
                    if (data.videoV.equals(thumbInfo.getVideoNumber())) {
                        // View更新
                        View view = listView.getChildAt(i - first);
                        mAdapter.setListItem((ListItem) view.getTag(),
                                thumbInfo, data);
                        break;
                    }
                }
                break;
            case MSG_ID_THUMBINFO_OCCURRED_ERROR:
                break;
            case MSG_ID_UPDATE_CACHE_DATA:
                if (!mHandler.hasMessages(MSG_ID_UPDATE_CACHE_DATA)) {
                    new UpdateCacheDataTask().execute();
                }
                break;
            default:
                assert false;
                break;
        }
        return true;
    }

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

        mStateManager.onCreate(savedInstanceState);

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

        mVideoCacheServiceConnection = NicoroApplication.getInstance(activity
                ).getVideoCacheServiceConnection();
        mVideoCacheServiceConnection.addCallback(mVideoCacheServiceCallback);
        mVideoCacheServiceConnection.bindVideoCacheService(new Intent(),
                mContext, TAG_VIDEO_CACHE_SERVICE);

        Resources res = 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);
        mResStringAccess = res.getString(R.string.access);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mLayoutInflater = inflater;
        View v = inflater.inflate(R.layout.access_history_list, container, false);
        mEmptyProgress = new ListEmptyProgressManager(v);
        mInfoCacheFile = ViewUtil.findViewById(v, R.id.info_cache_file);
        return v;
    }

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

        ListView listView = getListView();

        mAdapter = new AccessHistoryListAdapter();
        if (savedInstanceState != null) {
            mAdapter.onRestoreInstanceState(savedInstanceState);
        }
        setListAdapter(mAdapter);
        Resources res = getResources();
        final int colorBG = res.getColor(
                R.color.play_history_background);
        listView.setBackgroundColor(colorBG);
        listView.setCacheColorHint(colorBG);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                VideoLoader.ExternalInfoData data =
                    (VideoLoader.ExternalInfoData) parent.getItemAtPosition(position);
                openWebBrowserFragment(data);
            }
        });
    }

    private void openWebBrowserFragment(VideoLoader.ExternalInfoData data) {
        if (data == null) {
            return;
        }
        Activity a = getActivity();
        if (a instanceof WebBrowserFragmentStarter) {
            WebBrowserFragmentStarter starter =
                (WebBrowserFragmentStarter) a;
            // TODO 海外サイトどうする？
            String url = "http://www.nicovideo.jp/watch/" + data.videoV;

            starter.startWebBrowserFragment(url);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        mStateManager.onStart();

        ListView listView = getListView();
        registerForContextMenu(listView);
    }

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

        mHandler.sendEmptyMessage(MSG_ID_UPDATE_CACHE_DATA);

        new UpdateInfoCacheFileTask().execute();

        mEmptyProgress.showEmptyProgress();

        Intent deleteNotifyIntent =
            VideoCacheNotifyService.createIntentDeleteNotificationFinishCache(mContext);
        mContext.startService(deleteNotifyIntent);
    }

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

    @Override
    public void onStop() {
        super.onStop();
        mStateManager.onStop();

        ListView listView = getListView();
        unregisterForContextMenu(listView);
    }

    @Override
    public void onDestroy() {
        mStateManager.onDestroy();
        mAdapter.clearReserve();
        super.onDestroy();

        mVideoCacheServiceConnection.unbindVideoCacheService(
                mContext, TAG_VIDEO_CACHE_SERVICE);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mStateManager.onSaveInstanceState();
        mAdapter.onSaveInstanceState(outState);
    }

    private boolean isAnyCacheRunning(String videoNumber) {
        IVideoCacheService videoCacheService = mVideoCacheServiceConnection.getIVideoCacheService();
        if (videoCacheService == null) {
            return false;
        }
        return VideoCacheService.isAnyCacheRunning(videoNumber, videoCacheService);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        if (v instanceof ListView) {
            ListView listView = (ListView) v;
            AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;

            menu.add(Menu.NONE, R.id.menu_open_from_listmenu, Menu.NONE, R.string.menu_open);

            VideoLoader.ExternalInfoData data =
                (VideoLoader.ExternalInfoData) listView.getItemAtPosition(info.position);
            mInfoDataForContextMenu = data;

            if (isAnyCacheRunning(data.videoV)) {
                menu.add(Menu.NONE, R.id.menu_cache_stop, Menu.NONE, R.string.menu_stop_cache);
            }

            if (VideoLoader.hasAnyCacheFile(mContext, data.videoV)) {
                menu.add(Menu.NONE, R.id.menu_cache_delete, Menu.NONE, R.string.menu_delete_cache);
            }
        }
    }

    private void stopCache(final String videoNumber) {
        new CacheStopTask(getActivity(), videoNumber).execute();
    }

    private void deleteCache(final String videoNumber) {
        CacheDeleteTask.createCacheDeleteDialog(getActivity(), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Activity activity = getActivity();
                if (activity == null) {
                    Util.showErrorToast(mContext,
                            R.string.toast_delete_cache_one_fail);
                } else {
                    new CacheDeleteTask(activity, videoNumber).execute();
                }
            }
        }).show();
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_open_from_listmenu: {
                VideoLoader.ExternalInfoData data = mInfoDataForContextMenu;
                openWebBrowserFragment(data);
            } return true;
            case R.id.menu_cache_stop: {
                VideoLoader.ExternalInfoData data = mInfoDataForContextMenu;
                stopCache(data.videoV);
            } return true;
            case R.id.menu_cache_delete: {
                VideoLoader.ExternalInfoData data = mInfoDataForContextMenu;
                deleteCache(data.videoV);
            } return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    private class AccessHistoryListAdapter extends BaseAdapter {
        private ThumbnailCacher mThumbnailCacher;
        private ThumbInfoCacher mThumbInfoCacher;
        private ArrayList<VideoLoader.ExternalInfoData> mCacheData;
        private HashSet<ThumbInfo> mThumbInfoReserve;

        private DateFormat mDateFormat = DateFormat.getDateTimeInstance(
                DateFormat.LONG, DateFormat.DEFAULT);

        public AccessHistoryListAdapter() {
            NicoroApplication app = NicoroApplication.getInstance(
                    getActivity());
            mThumbnailCacher = app.getThumbnailCacher();
            mThumbInfoCacher = app.getThumbInfoCacher();
            mCacheData = null;
        }

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

        @Override
        public Object getItem(int position) {
            if (mCacheData == null) {
                return null;
            } else {
                return mCacheData.get(position);
            }
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            assert mCacheData != null;
            View view;
            ListItem listItem;
            if (convertView == null) {
                LayoutInflater inflater = mLayoutInflater;
                view = inflater.inflate(R.layout.access_history, parent, false);
                listItem = new ListItem();
                view.setTag(listItem);
                listItem.time = ViewUtil.findViewById(view, R.id.time);
                listItem.timeAccess = ViewUtil.findViewById(view, R.id.time_access);
                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);
                listItem.thumbnail = ViewUtil.findViewById(view, R.id.thumbnail);
            } else {
                view = convertView;
                listItem = (ListItem) convertView.getTag();
            }

            String videoNumber = mCacheData.get(position).videoV;
            ThumbInfo thumbInfo = mThumbInfoCacher.getThumbInfo(videoNumber);
            if (thumbInfo == null) {
                clearListItem(view, listItem, mCacheData.get(position));
                mThumbInfoCacher.loadThumbInfo(videoNumber,
                        new CallbackMessage<ThumbInfo, String>(
                                mHandler,
                                MSG_ID_THUMBINFO_FINISHED,
                                MSG_ID_THUMBINFO_OCCURRED_ERROR));
            } else {
                setListItem(listItem, thumbInfo, mCacheData.get(position));
            }

            return view;
        }

        void setCacheData(ArrayList<VideoLoader.ExternalInfoData> cacheData) {
            mCacheData = cacheData;
            mThumbInfoReserve = new HashSet<ThumbInfo>(cacheData.size());
//            // 先行してチェック＆リクエスト
//            for (VideoLoader.ExternalInfoData data : cacheData) {
//                ThumbInfo thumbInfo = mThumbInfoCacher.getThumbInfo(
//                        data.videoNumber);
//                if (thumbInfo == null) {
//                    mThumbInfoCacher.loadThumbInfo(data.videoNumber, mHandler,
//                            MSG_ID_THUMBINFO_FINISHED,
//                            MSG_ID_THUMBINFO_OCCURRED_ERROR);
//                } else {
//                    mThumbInfoReserve.add(thumbInfo);
//                }
//            }

            // TODO getListView()が可能なより広範で厳密なタイミングとしては、別の判定のほうが良いか？
            if (isResumed()) {
                getListView().invalidateViews();
            }
        }

        void setListItem(ListItem listItem, ThumbInfo thumbInfo,
                VideoLoader.ExternalInfoData data) {
            mThumbInfoReserve.add(thumbInfo);

            Date time = thumbInfo.getFirstRetrieveAsDate();
            if (time != null) {
                listItem.time.getTextBuilderWithClear().append(mDateFormat.format(
                        time)).append(" ").append(mResStringUpload);
                listItem.time.notifyUpdateText();
            }
            listItem.time.setVisibility(View.VISIBLE);

            Date timePlay = new Date(data.lastPlayed);
            listItem.timeAccess.getTextBuilderWithClear().append(mDateFormat.format(
                    timePlay)).append(" ").append(mResStringAccess);
            listItem.timeAccess.notifyUpdateText();
            listItem.timeAccess.setVisibility(View.VISIBLE);

            listItem.title.setText(thumbInfo.getParsedTitle());
            listItem.title.setVisibility(View.VISIBLE);

            listItem.view.getTextBuilderWithClear().append(mResStringInfoCountPlay
                    ).append(thumbInfo.getViewCounter());
            listItem.view.notifyUpdateText();
            listItem.view.setVisibility(View.VISIBLE);

            listItem.comment.getTextBuilderWithClear().append(mResStringInfoCountComment
                    ).append(thumbInfo.getCommentNum());
            listItem.comment.notifyUpdateText();
            listItem.comment.setVisibility(View.VISIBLE);

            listItem.mylist.getTextBuilderWithClear().append(mResStringInfoCountMylist
                    ).append(thumbInfo.getMylistCounter());
            listItem.mylist.notifyUpdateText();
            listItem.mylist.setVisibility(View.VISIBLE);

            listItem.length.setText(thumbInfo.getLength());
            listItem.length.setVisibility(View.VISIBLE);

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

            if (listItem.loading != null) {
                listItem.loading.setText("");
                listItem.loading.setVisibility(View.INVISIBLE);
            }
        }

        void clearListItem(View view, ListItem listItem,
                VideoLoader.ExternalInfoData data) {
            listItem.time.setVisibility(View.INVISIBLE);
            listItem.timeAccess.setVisibility(View.INVISIBLE);
            listItem.title.setVisibility(View.INVISIBLE);
            listItem.view.setVisibility(View.INVISIBLE);
            listItem.comment.setVisibility(View.INVISIBLE);
            listItem.mylist.setVisibility(View.INVISIBLE);
            listItem.length.setVisibility(View.INVISIBLE);
            listItem.thumbnail.setImageDrawable(null);
            listItem.thumbnail.setVisibility(View.INVISIBLE);

            if (listItem.loading == null) {
                ViewStub stubLoading = ViewUtil.findViewById(view, R.id.stub_loading);
                listItem.loading = ViewUtil.inflate(stubLoading);
            }
            listItem.loading.getTextBuilderWithClear().append(data.videoV
                    ).append(" loading...");
            listItem.loading.notifyUpdateText();
            listItem.loading.setVisibility(View.VISIBLE);
        }

        void clearReserve() {
            if (mThumbInfoReserve != null) {
                mThumbInfoReserve.clear();
            }
        }

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

        void onRestoreInstanceState(Bundle savedInstanceState) {
            // FIXME ViewPagerのFragment復帰でどうしてもクラッシュすることがあるので、いったん実装を変える
//            ArrayList<VideoLoader.ExternalInfoData> cacheData =
//                savedInstanceState.getParcelableArrayList(KEY_CACHE_DATA);
            byte[] gzipBytes = savedInstanceState.getByteArray(KEY_CACHE_DATA);
            ArrayList<VideoLoader.ExternalInfoData> cacheData =
                Util.inflateSerializable(gzipBytes);
            if (cacheData != null) {
                setCacheData(cacheData);
            }
        }
    }

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

        public VariableLabelView loading;
    }

    private class UpdateInfoCacheFileTask extends AsyncTask<Void, Void, Void> {
        private long mCacheSizeTotal;
        private long mCacheSizeSettings;
        private String mAvailableSize;

        @Override
        protected Void doInBackground(Void... params) {
            String streamTempDir = VideoLoader.getStreamTempDir(mContext);
            File dir = new File(streamTempDir);
            File[] filesCache = VideoLoader.getAllCacheFiles(dir);
            if (filesCache == null) {
                // キャッシュファイル無し
                mCacheSizeTotal = 0;
            } else {
                mCacheSizeTotal = VideoLoader.getTotalFileSize(
                        Arrays.asList(filesCache));
            }
            mCacheSizeSettings = VideoLoader.getCacheSizeSettings(mContext,
                    mSharedPreferences);
            StorageInfo storageInfo = new StorageInfo(streamTempDir);
            mAvailableSize = storageInfo.getAvailable();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            Activity activity = getActivity();
            if (activity == null) {
                return;
            }

            StringBuilder builder = new StringBuilder();
            builder.append(mCacheSizeTotal / (1024 * 1024))
                .append(" / ").append(mCacheSizeSettings / (1024 * 1024))
                .append(" MB");
            String cache = activity.getString(R.string.cache_size,
                    builder.toString());
            String free = activity.getString(R.string.storage_free_space,
                    mAvailableSize);
            mInfoCacheFile.setText(cache + " " + free);
        }
    }
}
