/*
 * Copyright (c) 2015 Ngewi Fet <ngewif@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gnucash.android.ui.report;

import android.content.Context;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;

import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.adapter.CommoditiesDbAdapter;
import org.gnucash.android.model.AccountType;
import org.gnucash.android.model.Commodity;
import org.gnucash.android.ui.common.Refreshable;
import org.joda.time.LocalDateTime;
import org.joda.time.Months;
import org.joda.time.Years;

import butterknife.BindView;
import butterknife.ButterKnife;

/**
 * Base class for report fragments.
 * <p>All report fragments should extend this class. At the minimum, reports must implement
 * {@link #getLayoutResource()}, {@link #getReportType()}, {@link #generateReport()}, {@link #displayReport()} and {@link #getTitle()}</p>
 * <p>Implementing classes should create their own XML layouts and provide it in {@link #getLayoutResource()}.
 * Then annotate any views in the resource using {@code @Bind} annotation from ButterKnife library.
 * This base activity will automatically call {@link ButterKnife#bind(View)} for the layout.
 * </p>
 * <p>Any custom information to be initialized for the report should be done in {@link #onActivityCreated(Bundle)} in implementing classes.
 * The report is then generated in {@link #onStart()}
 * </p>
 * @author Ngewi Fet <ngewif@gmail.com>
 */
public abstract class BaseReportFragment extends Fragment implements
        OnChartValueSelectedListener, ReportOptionsListener, Refreshable {

    /**
     * Color for chart with no data
     */
    public static final int NO_DATA_COLOR = Color.LTGRAY;

    protected static String TAG = "BaseReportFragment";

    /**
     * Reporting period start time
     */
    protected long mReportPeriodStart = -1;
    /**
     * Reporting period end time
     */
    protected long mReportPeriodEnd = -1;

    /**
     * Account type for which to display reports
     */
    protected AccountType mAccountType;

    /**
     * Commodity for which to display reports
     */
    protected Commodity mCommodity;

    /**
     * Intervals in which to group reports
     */
    protected ReportsActivity.GroupInterval mGroupInterval = ReportsActivity.GroupInterval.MONTH;

    /**
     * Pattern to use to display selected chart values
     */
    public static final String SELECTED_VALUE_PATTERN = "%s - %.2f (%.2f %%)";

    protected ReportsActivity mReportsActivity;

    @Nullable @BindView(R.id.selected_chart_slice) protected TextView mSelectedValueTextView;

    private AsyncTask<Void, Void, Void> mReportGenerator;

    /**
     * Return the title of this report
     * @return Title string identifier
     */
    public abstract @StringRes int getTitle();

    /**
     * Returns the layout resource to use for this report
     * @return Layout resource identifier
     */
    public abstract @LayoutRes int getLayoutResource();

    /**
     * Returns what kind of report this is
     * @return Type of report
     */
    public abstract ReportType getReportType();

    /**
     * Return {@code true} if this report fragment requires account type options.
     * <p>Sub-classes should implement this method. The base implementation returns {@code true}</p>
     * @return {@code true} if the fragment makes use of account type options, {@code false} otherwise
     */
    public boolean requiresAccountTypeOptions(){
        return true;
    }

    /**
     * Return {@code true} if this report fragment requires time range options.
     * <p>Base implementation returns true</p>
     * @return {@code true} if the report fragment requires time range options, {@code false} otherwise
     */
    public boolean requiresTimeRangeOptions(){
        return true;
    }

    /**
     * Generates the data for the report
     * <p>This method should not call any methods which modify the UI as it will be run in a background thread
     * <br>Put any code to update the UI in {@link #displayReport()}
     * </p>
     */
    protected abstract void generateReport();

    /**
     * Update the view after the report chart has been generated <br/>
     * Sub-classes should call to the base method
     */
    protected abstract void displayReport();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TAG = this.getClass().getSimpleName();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutResource(), container, false);
        ButterKnife.bind(this, view);
        return view;
    }

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

        ActionBar actionBar = ((AppCompatActivity)getActivity()).getSupportActionBar();
        assert actionBar != null;
        actionBar.setTitle(getTitle());

        setHasOptionsMenu(true);
        mCommodity = CommoditiesDbAdapter.getInstance()
                    .getCommodity(GnuCashApplication.getDefaultCurrencyCode());

        ReportsActivity reportsActivity = (ReportsActivity) getActivity();
        mReportPeriodStart = reportsActivity.getReportPeriodStart();
        mReportPeriodEnd = reportsActivity.getReportPeriodEnd();
        mAccountType = reportsActivity.getAccountType();
    }

    @Override
    public void onStart() {
        super.onStart();
        refresh();
    }

    @Override
    public void onResume() {
        super.onResume();
        mReportsActivity.setAppBarColor(getReportType().getTitleColor());
        mReportsActivity.toggleToolbarTitleVisibility();
        toggleBaseReportingOptionsVisibility();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!(getActivity() instanceof ReportsActivity))
            throw new RuntimeException("Report fragments can only be used with the ReportsActivity");
        else
            mReportsActivity = (ReportsActivity) getActivity();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        if (mReportGenerator != null)
            mReportGenerator.cancel(true);
    }

    private void toggleBaseReportingOptionsVisibility() {
        View timeRangeLayout = mReportsActivity.findViewById(R.id.time_range_layout);
        View dateRangeDivider = mReportsActivity.findViewById(R.id.date_range_divider);
        if (timeRangeLayout != null && dateRangeDivider != null) {
            int visibility = requiresTimeRangeOptions() ? View.VISIBLE : View.GONE;
            timeRangeLayout.setVisibility(visibility);
            dateRangeDivider.setVisibility(visibility);
        }

        View accountTypeSpinner = mReportsActivity.findViewById(R.id.report_account_type_spinner);
        int visibility = requiresAccountTypeOptions() ? View.VISIBLE : View.GONE;
        accountTypeSpinner.setVisibility(visibility);
    }


    /**
     * Calculates difference between two date values accordingly to {@code mGroupInterval}
     * @param start start date
     * @param end end date
     * @return difference between two dates or {@code -1}
     */
    protected int getDateDiff(LocalDateTime start, LocalDateTime end) {
        switch (mGroupInterval) {
            case QUARTER:
                int y = Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
                return getQuarter(end) - getQuarter(start) + y * 4;
            case MONTH:
                return Months.monthsBetween(start.withDayOfMonth(1).withMillisOfDay(0), end.withDayOfMonth(1).withMillisOfDay(0)).getMonths();
            case YEAR:
                return Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
            default:
                return -1;
        }
    }


    /**
     * Returns a quarter of the specified date
     * @param date date
     * @return a quarter
     */
    protected int getQuarter(LocalDateTime date) {
        return (date.getMonthOfYear() - 1) / 3 + 1;
    }


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

    @Override
    public void refresh() {
        if (mReportGenerator != null)
            mReportGenerator.cancel(true);

        mReportGenerator = new AsyncTask<Void, Void, Void>() {

            @Override
            protected void onPreExecute() {
                mReportsActivity.getProgressBar().setVisibility(View.VISIBLE);
            }

            @Override
            protected Void doInBackground(Void... params) {
                generateReport();
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                displayReport();
                mReportsActivity.getProgressBar().setVisibility(View.GONE);
            }
        };
        mReportGenerator.execute();
    }

    /**
     * Charts do not support account specific refreshes in general.
     * So we provide a base implementation which just calls {@link #refresh()}
     *
     * @param uid GUID of relevant item to be refreshed
     */
    @Override
    public void refresh(String uid) {
        refresh();
    }

    @Override
    public void onGroupingUpdated(ReportsActivity.GroupInterval groupInterval) {
        if (mGroupInterval != groupInterval) {
            mGroupInterval = groupInterval;
            refresh();
        }
    }

    @Override
    public void onTimeRangeUpdated(long start, long end) {
        if (mReportPeriodStart != start || mReportPeriodEnd != end) {
            mReportPeriodStart = start;
            mReportPeriodEnd = end;
            refresh();
        }
    }

    @Override
    public void onAccountTypeUpdated(AccountType accountType) {
        if (mAccountType != accountType) {
            mAccountType = accountType;
            refresh();
        }
    }

    @Override
    public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
        //nothing to see here, move along
    }

    @Override
    public void onNothingSelected() {
        if (mSelectedValueTextView != null)
            mSelectedValueTextView.setText(R.string.select_chart_to_view_details);
    }
}
