package ru.yandex.wmconsole.servantlet.graphics;

import java.awt.Color;
import java.awt.Dimension;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.wmconsole.data.info.DateCountInfo;
import ru.yandex.wmconsole.service.IWMCUserInfoService;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.servantlet.AuthenticationServantlet;
import ru.yandex.wmtools.common.util.TimeFilter;

/**
 * Created by IntelliJ IDEA.
 * Date: 18.02.2008
 * Time: 15:43:50
 * <p>
 * Returns JPEG of graph <quote>Number of users</quote>
 * <br>
 * Parameters:
 * <br>
 * reg_from optional Date (format "yyyy-MM-dd"): show from this date
 * <br>
 * reg_to optional Date (format "yyyy-MM-dd"): show till this date
 * <br>
 * show_diff optional boolean (default <code>false</code>):
 * <br>
 * If <code>true</code> show derivative, i.e. number of new users registred per day
 * </p>
 */
public class UsersGraphServantlet extends AuthenticationServantlet {
    private static final String PARAM_REG_FROM = "reg_from";
    private static final String PARAM_REG_TO = "reg_to";
    private static final String PARAM_SHOW_DIFF = "show_diff";

    private IWMCUserInfoService userInfoService;

    public byte[] createTestChart(boolean showDerivative, final TimeFilter timeFilter) throws InternalException {
        String chartTitle = "Пользователи сервиса Яндекс.Вебмастер";
        if (showDerivative) {
            chartTitle += " (изменение за день)";
        }
        else {
            chartTitle += " (число)";
        }
        final XYDataset totalUsersDataset = getUsersDataset(showDerivative, timeFilter);
        final XYDataset usersWithHostsDataset = getUsersWithHostsDataset(showDerivative, timeFilter);
        final XYDataset usersWithVerifiedHostsDataset = getUsersWithVerifiedHostsDataset(showDerivative, timeFilter);
        final XYDataset trueUsersDataset = getTrueUsersDataset(showDerivative, timeFilter);

        final JFreeChart chart = ChartFactory.createTimeSeriesChart(chartTitle, "Дата", "Число пользователей", totalUsersDataset, true, false, false);

        final XYPlot plot = chart.getXYPlot();
        plot.setDataset(0, totalUsersDataset);
        plot.setDataset(1, usersWithHostsDataset);
        plot.setDataset(2, usersWithVerifiedHostsDataset);
        plot.setDataset(3, trueUsersDataset);

        final StandardXYItemRenderer renderer1 = new StandardXYItemRenderer();
        renderer1.setSeriesPaint(0, Color.gray);
        plot.setRenderer(0, renderer1);

        final StandardXYItemRenderer renderer2 = new StandardXYItemRenderer();
        renderer2.setSeriesPaint(1, Color.blue);
        plot.setRenderer(1, renderer2);

        final StandardXYItemRenderer renderer3 = new StandardXYItemRenderer();
        renderer3.setSeriesPaint(2, Color.green);
        plot.setRenderer(2, renderer3);

        final StandardXYItemRenderer renderer4 = new StandardXYItemRenderer();
        renderer4.setSeriesPaint(3, Color.red);
        plot.setRenderer(3, renderer4);

        final DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setDateFormatOverride(new SimpleDateFormat("dd-MM-yy"));

        final ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(800, 570));
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ChartUtilities.writeChartAsJPEG(outputStream, chart, 800, 600);
        }
        catch (IOException e) {
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Cannot create writeChartAsJPEG!", e);
        }

        return outputStream.toByteArray();
    }

    private XYDataset getUsersDataset(boolean showDerivative, final TimeFilter timeFilter) throws InternalException {
        TimeSeries timeSeries = new TimeSeries("Число пользователей", Day.class);
        List<DateCountInfo> date2count = userInfoService.getNewUsersCount(timeFilter);
        Integer sum = 0;
        for (DateCountInfo info: date2count) {
            if (showDerivative) {
                timeSeries.add(new Day(info.getDate()), info.getCount());
            }
            else {
                sum += info.getCount();
                timeSeries.add(new Day(info.getDate()), sum);
            }
        }
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(timeSeries);

        return dataset;
    }

    private XYDataset getUsersWithHostsDataset(boolean showDerivative, final TimeFilter timeFilter) throws InternalException {
        TimeSeries timeSeries = new TimeSeries("Число пользователей с сайтами", Day.class);
        List<DateCountInfo> date2count = userInfoService.getUsersWithHostsCount(timeFilter);
        Integer sum = 0;
        for (DateCountInfo info: date2count) {
            if (showDerivative) {
                timeSeries.add(new Day(info.getDate()), info.getCount());
            }
            else {
                sum += info.getCount();
                timeSeries.add(new Day(info.getDate()), sum);
            }
        }
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(timeSeries);

        return dataset;
    }

    private XYDataset getUsersWithVerifiedHostsDataset(boolean showDerivative, final TimeFilter timeFilter) throws InternalException {
        TimeSeries timeSeries = new TimeSeries("Число пользователей с подтверждёнными сайтами", Day.class);
        List<DateCountInfo> date2count = userInfoService.getUsersWithVerifiedHostsCount(timeFilter);
        Integer sum = 0;
        for (DateCountInfo info: date2count) {
            if (showDerivative) {
                timeSeries.add(new Day(info.getDate()), info.getCount());
            }
            else {
                sum += info.getCount();
                timeSeries.add(new Day(info.getDate()), sum);
            }
        }
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(timeSeries);

        return dataset;
    }

    private XYDataset getTrueUsersDataset(boolean showDerivative, final TimeFilter timeFilter) throws InternalException {
        TimeSeries timeSeries = new TimeSeries("Число постоянных пользователей", Day.class);
        List<DateCountInfo> date2count = userInfoService.getTrueUsersCount2(timeFilter);
        Integer prev = null;
        for (DateCountInfo info: date2count) {
            if (showDerivative) {
                int diff = info.getCount();
                if (prev != null) {
                    diff -= prev;
                    timeSeries.add(new Day(info.getDate()), diff);
                }
                prev = info.getCount();
            }
            else {
                timeSeries.add(new Day(info.getDate()), info.getCount());
            }
        }
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(timeSeries);

        return dataset;
    }

    @Override
    public void doProcess(ServRequest req, ServResponse res, long userId) throws UserException, InternalException {
        Boolean showDerivative = req.getParamAsBoolean(PARAM_SHOW_DIFF, false);
        String fromDate = req.getParam(PARAM_REG_FROM, true);
        String toDate = req.getParam(PARAM_REG_TO, true);
        TimeFilter timeFilter;
        try {
            timeFilter = TimeFilter.create(fromDate, toDate);
        } catch (NumberFormatException e) {
            throw new UserException(UserProblem.ILLEGAL_VALUE_TYPE, "failed to parse params", e);
        }

        res.write(createTestChart(showDerivative, timeFilter));
    }

    @Required
    public void setUserInfoService(final IWMCUserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }
}
