/*
Copyright (C) 2013 [email protected]

This file is part of ComputationalEconomy.

ComputationalEconomy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ComputationalEconomy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ComputationalEconomy. If not, see <http://www.gnu.org/licenses/>.
 */

package io.github.uwol.compecon.dashboard.panel;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.util.Map;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.DefaultHighLowDataset;
import org.jfree.data.xy.XYDataset;

import io.github.uwol.compecon.economy.materia.GoodType;
import io.github.uwol.compecon.economy.sectors.financial.Currency;
import io.github.uwol.compecon.engine.applicationcontext.ApplicationContext;
import io.github.uwol.compecon.engine.statistics.NotificationListenerModel.ModelListener;
import io.github.uwol.compecon.engine.statistics.PricesModel;
import io.github.uwol.compecon.engine.statistics.PricesModel.PriceModel;
import io.github.uwol.compecon.math.production.ConvexProductionFunction.ConvexProductionFunctionTerminationCause;

public class IndustriesPanel extends AbstractChartsPanel implements ModelListener {

	private static final long serialVersionUID = 1L;

	public class IndustriesPanelForCurrency extends JPanel implements ModelListener {

		private static final long serialVersionUID = 1L;

		public class IndustriesPanelForGoodTypeInCurrency extends JPanel implements ModelListener {

			private static final long serialVersionUID = 1L;

			protected final Currency currency;

			protected final GoodType goodType;

			protected JPanel marketDepthPanel;

			protected JPanel priceTimeSeriesPanel;

			public IndustriesPanelForGoodTypeInCurrency(final Currency currency, final GoodType goodType) {
				this.currency = currency;
				this.goodType = goodType;

				setLayout(new GridLayout(0, 2));

				this.add(createProductionPanel(currency, goodType));
				this.add(createProductionFunctionMechanicsPanel(currency, goodType));
				this.add(createFactoryBalanceSheetPanel(currency, goodType));
				this.add(createGoodTypeSupplyPanel(currency, goodType));
				this.add(createPricingBehaviourMechanicsPanel(currency, goodType));

				// only capital goods are depreciated
				if (goodType.isDurable()) {
					this.add(createCapitalDepreciationPanel(currency, goodType));
				}

				ApplicationContext.getInstance().getModelRegistry().getNationalEconomyModel(currency).pricesModel
						.registerListener(this);
				// no registration with the market depth model, as they call
				// listeners synchronously

				notifyListener();
			}

			@Override
			public synchronized void notifyListener() {
				if (isShowing()) {
					// prices panel
					if (priceTimeSeriesPanel != null) {
						this.remove(priceTimeSeriesPanel);
					}
					priceTimeSeriesPanel = createPriceTimeSeriesChartPanel(currency, goodType);
					this.add(priceTimeSeriesPanel);

					// market depth panel
					if (marketDepthPanel != null) {
						this.remove(marketDepthPanel);
					}
					marketDepthPanel = createMarketDepthPanel(currency, goodType);
					this.add(marketDepthPanel);

					validate();
					repaint();
				}
			}
		}

		protected final Currency currency;

		protected final JTabbedPane jTabbedPaneGoodType = new JTabbedPane();

		public IndustriesPanelForCurrency(final Currency currency) {
			setLayout(new BorderLayout());

			this.currency = currency;
			this.add(jTabbedPaneGoodType, BorderLayout.CENTER);
			for (final GoodType outputGoodType : GoodType.values()) {
				if (!GoodType.LABOURHOUR.equals(outputGoodType)) {
					// a model has to exist for this panel; might be not the
					// case when certain good types are unused in simulation
					if (ApplicationContext.getInstance().getModelRegistry().getNationalEconomyModel(currency)
							.getIndustryModel(outputGoodType) != null) {
						final IndustriesPanelForGoodTypeInCurrency panelForGoodType = new IndustriesPanelForGoodTypeInCurrency(
								currency, outputGoodType);
						jTabbedPaneGoodType.addTab(outputGoodType.name(), panelForGoodType);
					}
				}
			}

			jTabbedPaneGoodType.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(final ChangeEvent e) {
					if (e.getSource() instanceof JTabbedPane) {
						final JTabbedPane pane = (JTabbedPane) e.getSource();
						final IndustriesPanelForGoodTypeInCurrency selectedComponent = (IndustriesPanelForGoodTypeInCurrency) pane
								.getSelectedComponent();
						selectedComponent.notifyListener();
					}
				}
			});
		}

		@Override
		public void notifyListener() {
			if (isShowing()) {
				final IndustriesPanelForGoodTypeInCurrency industryPanel = (IndustriesPanelForGoodTypeInCurrency) jTabbedPaneGoodType
						.getSelectedComponent();
				industryPanel.notifyListener();
			}
		}
	}

	protected final JTabbedPane jTabbedPaneCurrency = new JTabbedPane();

	public IndustriesPanel() {
		setLayout(new BorderLayout());
		for (final Currency currency : Currency.values()) {
			final IndustriesPanelForCurrency panelForCurrency = new IndustriesPanelForCurrency(currency);
			jTabbedPaneCurrency.addTab(currency.getIso4217Code(), panelForCurrency);
		}

		jTabbedPaneCurrency.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(final ChangeEvent e) {
				if (e.getSource() instanceof JTabbedPane) {
					final JTabbedPane pane = (JTabbedPane) e.getSource();
					final IndustriesPanelForCurrency selectedComponent = (IndustriesPanelForCurrency) pane
							.getSelectedComponent();
					selectedComponent.notifyListener();
				}
			}
		});

		add(jTabbedPaneCurrency, BorderLayout.CENTER);
	}

	protected ChartPanel createCapitalDepreciationPanel(final Currency currency, final GoodType outputGoodType) {
		final TimeSeriesCollection timeSeriesCollection = new TimeSeriesCollection();

		timeSeriesCollection
				.addSeries(ApplicationContext.getInstance().getModelRegistry().getNationalEconomyModel(currency)
						.getIndustryModel(outputGoodType).capitalDepreciationModel.getTimeSeries());

		final JFreeChart chart = ChartFactory.createTimeSeriesChart(outputGoodType.toString() + " Capital Depreciation",
				"Date", "Capital Depreciation", timeSeriesCollection, true, true, false);
		configureChart(chart);
		return new ChartPanel(chart);
	}

	protected ChartPanel createGoodTypeSupplyPanel(final Currency currency, final GoodType outputGoodType) {
		final TimeSeriesCollection timeSeriesCollection = new TimeSeriesCollection();

		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getPricingBehaviourModel(outputGoodType).offerModel.getTimeSeries());
		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getPricingBehaviourModel(outputGoodType).soldModel.getTimeSeries());
		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getIndustryModel(outputGoodType).inventoryModel.getTimeSeries());
		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getIndustryModel(outputGoodType).outputModel.getTimeSeries());

		final JFreeChart chart = ChartFactory.createTimeSeriesChart(outputGoodType.toString() + " Supply", "Date",
				"Supply", timeSeriesCollection, true, true, false);
		configureChart(chart);
		return new ChartPanel(chart);
	}

	protected ChartPanel createMarketDepthPanel(final Currency currency, final GoodType goodType) {
		final XYDataset dataset = ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).marketDepthModel.getMarketDepthDataset(currency, goodType);
		final JFreeChart chart = ChartFactory.createXYStepAreaChart(goodType + " Market Depth", "Price", "Volume",
				dataset, PlotOrientation.VERTICAL, true, true, false);
		return new ChartPanel(chart);
	}

	protected ChartPanel createPriceTimeSeriesChartPanel(final Currency currency, final GoodType goodType) {
		final JFreeChart priceChart = ChartFactory.createCandlestickChart(goodType + " Prices", "Time",
				"Price in " + currency.getIso4217Code(), getDefaultHighLowDataset(currency, goodType), false);
		final ChartPanel chartPanel = new ChartPanel(priceChart);
		chartPanel.setDomainZoomable(true);
		chartPanel.setPreferredSize(new java.awt.Dimension(800, 400));
		return chartPanel;
	}

	protected ChartPanel createProductionFunctionMechanicsPanel(final Currency currency,
			final GoodType outputGoodType) {
		final TimeSeriesCollection timeSeriesCollection = new TimeSeriesCollection();

		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getIndustryModel(outputGoodType).budgetModel.getTimeSeries());
		for (final ConvexProductionFunctionTerminationCause terminationCause : ConvexProductionFunctionTerminationCause
				.values()) {
			timeSeriesCollection
					.addSeries(ApplicationContext.getInstance().getModelRegistry().getNationalEconomyModel(currency)
							.getIndustryModel(outputGoodType).convexProductionFunctionTerminationCauseModels
									.get(terminationCause).getTimeSeries());
		}

		// budget is correct here, as the chart illustrates budget
		// emergence from these causes
		final JFreeChart chart = ChartFactory.createTimeSeriesChart(
				outputGoodType.toString() + " Production Function Mechanics", "Date", "Budget Spent",
				timeSeriesCollection, true, true, false);
		configureChart(chart);
		return new ChartPanel(chart);
	}

	protected ChartPanel createProductionPanel(final Currency currency, final GoodType outputGoodType) {
		final TimeSeriesCollection timeSeriesCollection = new TimeSeriesCollection();

		timeSeriesCollection.addSeries(ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getIndustryModel(outputGoodType).outputModel.getTimeSeries());
		for (final GoodType inputGoodType : ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).getIndustryModel(outputGoodType).inputModels.keySet()) {
			timeSeriesCollection
					.addSeries(ApplicationContext.getInstance().getModelRegistry().getNationalEconomyModel(currency)
							.getIndustryModel(outputGoodType).inputModels.get(inputGoodType).getTimeSeries());
		}

		final JFreeChart chart = ChartFactory.createTimeSeriesChart(outputGoodType.toString() + " Production", "Date",
				"Output", timeSeriesCollection, true, true, false);
		configureChart(chart);
		chart.addSubtitle(new TextTitle("Inputs: " + ApplicationContext.getInstance().getInputOutputModel()
				.getProductionFunction(outputGoodType).getInputGoodTypes().toString()));
		return new ChartPanel(chart);
	}

	protected DefaultHighLowDataset getDefaultHighLowDataset(final Currency currency, final GoodType goodType) {
		final PricesModel pricesModel = ApplicationContext.getInstance().getModelRegistry()
				.getNationalEconomyModel(currency).pricesModel;

		final Map<GoodType, PriceModel> priceModelsForGoodType = pricesModel.getPriceModelsForGoodTypes();
		final PriceModel priceModel = priceModelsForGoodType.get(goodType);

		if (priceModel != null) {
			return new DefaultHighLowDataset("", priceModel.getDate(), priceModel.getHigh(), priceModel.getLow(),
					priceModel.getOpen(), priceModel.getClose(), priceModel.getVolume());
		}

		return null;
	}

	@Override
	public void notifyListener() {
		if (isShowing()) {
			final IndustriesPanelForCurrency industryPanel = (IndustriesPanelForCurrency) jTabbedPaneCurrency
					.getSelectedComponent();
			industryPanel.notifyListener();
		}
	}
}