package com.example.javancss;

import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;

import org.apache.wicket.markup.html.panel.Panel;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;

import com.example.javancss.web.AggregatedMetricsPanel;
import com.example.javancss.web.MetricsOverviewPanel;
import com.example.javancss.web.MetricsReportPanel;
import com.example.javancss.web.MetricsStatisticsPanel;
import com.example.javancss.web.MetricsSummaryPanel;
import com.pmease.quickbuild.Context;
import com.pmease.quickbuild.aggregationsupport.Aggregation;
import com.pmease.quickbuild.extensionpoint.AggregationSupport;
import com.pmease.quickbuild.extensionpoint.BuildOverviewContribution;
import com.pmease.quickbuild.extensionpoint.BuildSummaryContribution;
import com.pmease.quickbuild.extensionpoint.BuildTabContribution;
import com.pmease.quickbuild.extensionpoint.ConfigurationOverviewContribution;
import com.pmease.quickbuild.extensionpoint.StatisticsTabContribution;
import com.pmease.quickbuild.extensionpoint.StepProvider;
import com.pmease.quickbuild.extensionpoint.support.PanelCreator;
import com.pmease.quickbuild.model.Configuration;
import com.pmease.quickbuild.pluginsupport.AbstractPlugin;
import com.pmease.quickbuild.stepsupport.Step;
import com.pmease.quickbuild.util.BeanUtils;
import com.pmease.quickbuild.util.DateUtils;
import com.pmease.quickbuild.util.FileUtils;
import com.pmease.quickbuild.util.LockUtils;
import com.pmease.quickbuild.util.datastore.FileDb;
import com.pmease.quickbuild.web.component.tabbedpanel.Tab;

public class JavaNCSSPlugin extends AbstractPlugin {
	
	public static final String METRICS_FILE = "javancss_metrics.xml";
	
	public static final String STATISTICS_FILE = "javancss_statistics.xml";
	
	public static final String AGGREGATION_DIR = "javancss_aggregations";

	@Override
	public Object[] getExtensions() {
		return new Object[]{
			new StepProvider() {

				@Override
				public Class<? extends Step> getStepClass() {
					return JavaNCSSPublishStep.class;
				}
				
			}, 
			new AggregationSupport() {

				@Override
				public Class<? extends Aggregation> getAggregationClass() {
					return JavaNCSSAggregation.class;
				}

				@Override
				public void cleanupAggregations(Configuration configuration) {
					File aggregationDir = new File(configuration.getPublishDir(), AGGREGATION_DIR);
					Lock lock = LockUtils.lockForWrite(aggregationDir);
					try {
						FileUtils.deleteDir(aggregationDir);
					} finally {
						lock.unlock();
					}
				}
				
			},
			new JavaNCSSStatisticsSupport(), 
			new BuildOverviewContribution() {
				
				public int getOrder() {
					return 500;
				}

				public List<PanelCreator> getPanelCreators() {
					List<PanelCreator> creators = new ArrayList<PanelCreator>();
					File reportFile = new File(Context.getBuild().getPublishDir(), METRICS_FILE); 
					Lock lock = LockUtils.lockForRead(reportFile);
					try {
						if (reportFile.exists()) { 
							final Metrics report = (Metrics) BeanUtils.readFile(reportFile);
							creators.add(new PanelCreator() {

								public Panel newPanel(String id) {
									return new MetricsOverviewPanel(id, report);
								}
								
							});
						}
					} finally {
						lock.unlock();
					}
					return creators;
				}
			},
			new ConfigurationOverviewContribution() {

				public int getOrder() {
					return 500;
				}

				public List<PanelCreator> getPanelCreators() {
					List<PanelCreator> creators = new ArrayList<PanelCreator>();
					File aggregationDir = new File(Context.getConfiguration().getPublishDir(), 
							AGGREGATION_DIR);
					Lock lock = LockUtils.lockForRead(aggregationDir);
					try {
						if (aggregationDir.exists()) {
							for (final File dbDir: aggregationDir.listFiles()) {
								if (!(Context.getConfiguration().findAggregation(dbDir.getName()) 
										instanceof JavaNCSSAggregation)) {
									continue;
								}
								final Metrics metrics = (Metrics) new FileDb(dbDir, new AggregationDbMigrator()).read(
										new FileDb.Callback() {
									
									public Object execute(Connection conn) throws SQLException {
										Statement stmt = conn.createStatement();
										ResultSet rs = stmt.executeQuery("select sum(source_lines), " +
												"sum(comment_lines) from metrics where day=0");
										Metrics metrics = new Metrics();
										if (rs.next()) {
											metrics.setSourceLines(rs.getInt(1));
											metrics.setCommentLines(rs.getInt(2));
										}
										return metrics;
									}
								});
								creators.add(new PanelCreator() {

									public Panel newPanel(String id) {
										return new AggregatedMetricsPanel(id, dbDir.getName(), metrics);
									}
									
								});
							}
						}
						return creators;
					} finally {
						lock.unlock();
					}
				}
				
			},
			new BuildSummaryContribution() {

				public int getOrder() {
					return 1000;
				}

				public List<PanelCreator> getPanelCreators() {
					List<PanelCreator> creators = new ArrayList<PanelCreator>();
					File metricsFile = new File(Context.getBuild().getPublishDir(), METRICS_FILE); 
					Lock lock = LockUtils.lockForRead(metricsFile);
					try {
						if (metricsFile.exists()) { 
							final Metrics metrics = (Metrics) BeanUtils.readFile(metricsFile);
							creators.add(new PanelCreator() {
	
								public Panel newPanel(String id) {
									return new MetricsSummaryPanel(id, Context.getBuild().getId(), metrics);
								}
								
							});
						}
					} finally {
						lock.unlock();
					}
					return creators;
				}
				
			},
			new BuildTabContribution() {

				private static final long serialVersionUID = 1L;

				public int getOrder() {
					return 1000;
				}

				public List<Tab> getTabs() {
					List<Tab> tabs = new ArrayList<Tab>();
					File metricsFile = new File(Context.getBuild().getPublishDir(), METRICS_FILE);
					Lock lock = LockUtils.lockForRead(metricsFile);
					try {
						if (metricsFile.exists()) {
							final Metrics metrics = (Metrics) BeanUtils.readFile(metricsFile);
							tabs.add(new Tab("JavaNCSS") {

								private static final long serialVersionUID = 1L;

								@Override
								public Panel getPanel(String id, Map<String, String> params) {
									return new MetricsReportPanel(id, metrics);
								}
								
							});
						}
					} finally {
						lock.unlock();
					}
					return tabs;
				}
				
			},
			new StatisticsTabContribution() {

				private static final long serialVersionUID = 1L;

				public int getOrder() {
					return 1000;
				}

				public List<Tab> getTabs() {
					List<Tab> tabs = new ArrayList<Tab>();
					File statsFile = new File(Context.getConfiguration().getPublishDir(), 
							STATISTICS_FILE);
					Lock lock = LockUtils.lockForRead(statsFile);
					try {
						if (statsFile.exists()) {
							final MetricsStatistics stats = (MetricsStatistics) BeanUtils.readFile(statsFile);
							tabs.add(new Tab("JavaNCSS") {

								private static final long serialVersionUID = 1L;

								@Override
								public Panel getPanel(String id, Map<String, String> params) {
									DefaultCategoryDataset dataset = new DefaultCategoryDataset();
									
									for (MetricsStatistics.Snapshot snapshot: stats.getSnapshots()) {
										dataset.addValue(snapshot.getMetrics().getCommentLines(), 
												"comment lines", snapshot.getBuildVersion());
										dataset.addValue(snapshot.getMetrics().getSourceLines(), 
												"source lines", snapshot.getBuildVersion());
									}
									
									return new MetricsStatisticsPanel(id, dataset, "Build");
								}
								
							});
						}
					} finally {
						lock.unlock();
					}
					
					File aggregationDir = new File(Context.getConfiguration().getPublishDir(), 
							AGGREGATION_DIR);
					lock = LockUtils.lockForRead(aggregationDir);
					try {
						if (aggregationDir.exists()) {
							for (final File dbDir: aggregationDir.listFiles()) {
								if (!(Context.getConfiguration().findAggregation(dbDir.getName()) 
										instanceof JavaNCSSAggregation)) {
									continue;
								}
								final CategoryDataset dataset = (CategoryDataset) new FileDb(dbDir, 
										new AggregationDbMigrator()).read(new FileDb.Callback() {
									
									public Object execute(Connection conn) throws SQLException {
										DefaultCategoryDataset dataset = new DefaultCategoryDataset();
										
										Statement stmt = conn.createStatement();
										ResultSet rs = stmt.executeQuery("select * from metrics where day!=0 " +
												"order by day desc");
										Map<Integer, Map<Integer, PreciseMetrics>> metricsByDay = 
											new HashMap<Integer, Map<Integer, PreciseMetrics>>();
										Set<Integer> checkDays = new HashSet<Integer>();
										while (rs.next()) {
											int count = rs.getInt("count");
											int day = rs.getInt("day");
											int configuration = rs.getInt("configuration");
											int sourceLines = rs.getInt("source_lines");
											int commentLines = rs.getInt("comment_lines");
											checkDays.add(day);
											for (int each: checkDays) {
												Map<Integer, PreciseMetrics> metricsByConfigurations = 
													metricsByDay.get(each);
												if (metricsByConfigurations == null) {
													metricsByConfigurations = new HashMap<Integer, PreciseMetrics>();
													metricsByDay.put(each, metricsByConfigurations);
												}
												if (!metricsByConfigurations.containsKey(configuration)) {
													PreciseMetrics metrics = new PreciseMetrics();
													metrics.setSourceLines(sourceLines*1.0/count);
													metrics.setCommentLines(commentLines*1.0/count);
													metricsByConfigurations.put(configuration, metrics);
												}
												
											}
										}
										rs.close();
										
										List<Integer> sortedDays = new ArrayList<Integer>(checkDays);
										Collections.sort(sortedDays);
										for (int day: sortedDays) {
											Map<Integer, PreciseMetrics> metricsByConfiguration = metricsByDay.get(day);
											double totalSourceLines = 0;
											double totalCommentLines = 0;
											for (PreciseMetrics metrics: metricsByConfiguration.values()) {
												totalSourceLines += metrics.getSourceLines();
												totalCommentLines += metrics.getCommentLines();
											}
											Date date = new Date(day*24*3600*1000L);
											String displayDate = DateUtils.formatDate(date);
											dataset.addValue((int)totalCommentLines, "comment lines", displayDate);
											dataset.addValue((int)totalSourceLines, "source lines", displayDate);
										}
										return dataset;
									}
								});
								tabs.add(new Tab(dbDir.getName()) {

									private static final long serialVersionUID = 1L;

									@Override
									public Panel getPanel(String id, Map<String, String> params) {
										return new MetricsStatisticsPanel(id, dataset, "Date");
									}
									
								});
							}
						}
						
						return tabs;
					} finally {
						lock.unlock();
					}
				}
				
			}
		};
	}

	@Override
	public Class<?> getSettingClass() {
		return PluginSetting.class;
	}

	private static class PreciseMetrics {
		private double sourceLines;
		
		private double commentLines;

		public double getSourceLines() {
			return sourceLines;
		}

		public void setSourceLines(double sourceLines) {
			this.sourceLines = sourceLines;
		}

		public double getCommentLines() {
			return commentLines;
		}

		public void setCommentLines(double commentLines) {
			this.commentLines = commentLines;
		}
		
	}
}
