package io.github.droidkaigi.confsched.fragment;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
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 org.parceler.Parcels;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.inject.Inject;

import io.github.droidkaigi.confsched.R;
import io.github.droidkaigi.confsched.activity.ActivityNavigator;
import io.github.droidkaigi.confsched.activity.SearchActivity;
import io.github.droidkaigi.confsched.api.DroidKaigiClient;
import io.github.droidkaigi.confsched.dao.SessionDao;
import io.github.droidkaigi.confsched.databinding.FragmentSessionsBinding;
import io.github.droidkaigi.confsched.model.MainContentStateBrokerProvider;
import io.github.droidkaigi.confsched.model.Page;
import io.github.droidkaigi.confsched.model.Session;
import io.github.droidkaigi.confsched.util.DateUtil;
import io.github.droidkaigi.confsched.util.LocaleUtil;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;

public class SessionsFragment extends BaseFragment implements StackedPageListener {

    public static final String TAG = SessionsFragment.class.getSimpleName();
    private static final String ARG_SHOULD_REFRESH = "should_refresh";
    private static final int REQ_SEARCH = 2;
    @Inject
    DroidKaigiClient client;
    @Inject
    SessionDao dao;
    @Inject
    CompositeSubscription compositeSubscription;
    @Inject
    ActivityNavigator activityNavigator;
    @Inject
    MainContentStateBrokerProvider brokerProvider;
    private SessionsPagerAdapter adapter;
    private FragmentSessionsBinding binding;
    private boolean shouldRefresh;
    private OnChangeSessionListener onChangeSessionListener = session -> { /*no op*/ };

    public static SessionsFragment newInstance() {
        return newInstance(false);
    }

    public static SessionsFragment newInstance(boolean shouldRefresh) {
        SessionsFragment fragment = new SessionsFragment();
        Bundle args = new Bundle();
        args.putBoolean(ARG_SHOULD_REFRESH, shouldRefresh);
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = FragmentSessionsBinding.inflate(inflater, container, false);
        setHasOptionsMenu(true);
        initEmptyView();
        compositeSubscription.add(loadData());
        compositeSubscription.add(fetchAndSave());
        return binding.getRoot();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            this.shouldRefresh = getArguments().getBoolean(ARG_SHOULD_REFRESH, false);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        getComponent().inject(this);
        if (context instanceof OnChangeSessionListener) {
            onChangeSessionListener = (OnChangeSessionListener) context;
        }
    }

    private void initEmptyView() {
        binding.emptyViewButton.setOnClickListener(v -> brokerProvider.get().set(Page.ALL_SESSIONS));
    }

    private Subscription fetchAndSave() {
        return client.getSessions(LocaleUtil.getCurrentLanguageId(getActivity()))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        dao::updateAllAsync,
                        throwable -> Log.e(TAG, "Failed to fetchAndSave.", throwable)
                );
    }

    protected Subscription loadData() {
        showLoadingView();
        Observable<List<Session>> cachedSessions = dao.findAll();
        return cachedSessions.flatMap(sessions -> {
            if (shouldRefresh || sessions.isEmpty()) {
                return client.getSessions(LocaleUtil.getCurrentLanguageId(getActivity()))
                        .doOnNext(dao::updateAllAsync);
            } else {
                return Observable.just(sessions);
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        this::onLoadDataSuccess,
                        this::onLoadDataFailure
                );
    }

    private void onLoadDataSuccess(List<Session> sessions) {
        Log.i(TAG, "Sessions Load succeeded.");
        // TODO This is temporary bug fix. https://github.com/konifar/droidkaigi2016/issues/264
        if (shouldRefresh) {
            sessions = dao.findAll().toBlocking().single();
        }
        groupByDateSessions(sessions);
    }

    private void onLoadDataFailure(Throwable throwable) {
        Log.e(TAG, "Sessions Load failed", throwable);
        Snackbar.make(binding.containerMain, R.string.sessions_network_error, Snackbar.LENGTH_LONG).show();
    }

    protected void showEmptyView() {
        binding.emptyView.setVisibility(View.VISIBLE);
    }

    protected void hideEmptyView() {
        binding.emptyView.setVisibility(View.GONE);
    }

    protected void showLoadingView() {
        binding.progressBarContainer.setVisibility(View.VISIBLE);
    }

    protected void hideLoadingView() {
        binding.progressBarContainer.setVisibility(View.GONE);
    }

    protected void groupByDateSessions(List<Session> sessions) {
        Map<String, List<Session>> sessionsByDate = new TreeMap<>();
        for (Session session : sessions) {
            String key = DateUtil.getMonthDate(session.stime, getActivity());
            if (sessionsByDate.containsKey(key)) {
                sessionsByDate.get(key).add(session);
            } else {
                List<Session> list = new ArrayList<>();
                list.add(session);
                sessionsByDate.put(key, list);
            }
        }

        adapter = new SessionsPagerAdapter(getFragmentManager());

        for (Map.Entry<String, List<Session>> e : sessionsByDate.entrySet()) {
            addFragment(e.getKey(), e.getValue());
        }

        binding.viewPager.setAdapter(adapter);
        binding.tabLayout.setupWithViewPager(binding.viewPager);
        binding.tabLayout.setOnTabSelectedListener(new CustomViewPagerOnTabSelectedListener(binding.viewPager));

        hideLoadingView();
        if (sessions.isEmpty()) {
            showEmptyView();
        } else {
            hideEmptyView();
        }
    }

    protected SessionsTabFragment createTabFragment(List<Session> sessions) {
        return SessionsTabFragment.newInstance(sessions);
    }

    private void addFragment(String title, List<Session> sessions) {
        SessionsTabFragment fragment = createTabFragment(sessions);
        adapter.add(title, fragment);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
        menuInflater.inflate(R.menu.menu_sessions, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.item_search:
                activityNavigator.showSearch(this, REQ_SEARCH);
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode != REQ_SEARCH) {
            return;
        }
        if (resultCode != Activity.RESULT_OK) {
            return;
        }
        List<Session> statusChangedSession = Parcels
                .unwrap(data.getParcelableExtra(SearchActivity.RESULT_STATUS_CHANGED_SESSIONS));
        if (statusChangedSession == null || statusChangedSession.isEmpty()) {
            return;
        }
        onChangeSessionListener.onChangeSession(statusChangedSession);
        compositeSubscription.add(loadData());
    }

    @Override
    public void onTop() {
        compositeSubscription.add(loadData());
    }

    public interface OnChangeSessionListener {
        void onChangeSession(List<Session> sessions);
    }

    private class SessionsPagerAdapter extends FragmentStatePagerAdapter {

        private List<SessionsTabFragment> fragments = new ArrayList<>();
        private List<String> titles = new ArrayList<>();

        public SessionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        @Nullable
        public Fragment getItem(int position) {
            if (position >= 0 && position < fragments.size()) {
                return fragments.get(position);
            }
            return null;
        }

        @Override
        public int getCount() {
            return fragments.size();
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return titles.get(position);
        }

        public void add(String title, SessionsTabFragment fragment) {
            fragments.add(fragment);
            titles.add(title);
            notifyDataSetChanged();
        }

    }

    private class CustomViewPagerOnTabSelectedListener extends TabLayout.ViewPagerOnTabSelectedListener {

        public CustomViewPagerOnTabSelectedListener(ViewPager viewPager) {
            super(viewPager);
        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {
            super.onTabReselected(tab);
            SessionsTabFragment fragment = (SessionsTabFragment) adapter.getItem(tab.getPosition());
            if (fragment != null) {
                fragment.scrollUpToTop();
            }
        }

    }

}