package org.pmdreports.manager;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.pmdreports.dao.ProjectDAO;
import org.pmdreports.dao.RulesetDAO;
import org.pmdreports.dao.StatisticsDAO;
import org.pmdreports.model.Component;
import org.pmdreports.model.Project;
import org.pmdreports.model.Ruleset;
import org.pmdreports.model.Statistics;
import org.pmdreports.util.DateUtil;

/**
 * Manage a list of components and their statistics. This is a Singleton which
 * is accessed by different Ant tasks.
 * 
 * Copyright Summa Technologies 2007
 * 
 * @author Ben Northrop
 */
public class StatisticsManager {

	// DATA MEMBERS
	private static StatisticsManager instance = new StatisticsManager();

	private final StatisticsDAO statisticsDAO;

	private final ConfigManager configManager = ConfigManager.getInstance();

	private final List<Project> projects;

	// CONSTRUCTOR
	/**
	 * Private constructor for Singleton.
	 */
	private StatisticsManager() {
		RulesetDAO rulesetDAO = new RulesetDAO(configManager.getRulesetsFile());
		List<Ruleset> rulesets = rulesetDAO.retrieveRulesets();

		ProjectDAO projectDAO = new ProjectDAO(configManager.getProjectsFile());
		projects = projectDAO.retrieveProjectConfig();

		statisticsDAO = new StatisticsDAO(configManager.getDatabase(),
				rulesets, projects);

	}

	// METHODS
	/**
	 * Factory method to instantiate.
	 */
	public static StatisticsManager getInstance() {
		return instance;
	}

	/**
	 * Add a component to the list.
	 */
	public void addComponentStatistics(Statistics stats) {
		statisticsDAO.insert(stats);
	}

	/**
	 * Return the list of component statistics.
	 */
	public Collection<Statistics> getStatisticsForToday() {
		return statisticsDAO.retrieve(new Date());
	}

	/**
	 * Return the list of statistics for a given component.
	 */
	public Collection<Statistics> getStatistics(Component component) {
		return statisticsDAO.retrieve(component);
	}

	/**
	 * Return a list of all the components in the file.
	 */
	public Collection<Component> getComponents() {
		return statisticsDAO.retrieveComponents();
	}

	/**
	 * Return a list of all the projects in the file.
	 */
	public Collection<Project> getProjects() {
		return statisticsDAO.retrieveProjects();
	}

	/**
	 * Return true if only one days worth of statistics have been stored.
	 */
	public boolean hasOnlyOneDaysStatistics() {
		Collection<Date> dates = this.getDates();
		if (dates == null) {
			throw new IllegalStateException("No dates found for statistics!");
		}

		return dates.size() == 1;
	}

	/**
	 * Returns a list of components with statistics in order of date (??).
	 */
	public Map<Component, Collection<Statistics>> getAllStatistics() {
		Map<Component, Collection<Statistics>> componentMap = new HashMap<Component, Collection<Statistics>>();
		Collection<Component> components = getComponents();
		for (Component component : components) {
			componentMap.put(component, getStatistics(component));
		}

		return componentMap;
	}

	/**
	 * Combine all stats for a given project for today.
	 */
	public Collection<Statistics> getStatisticsForToday(Project project) {
		if (project == null) {
			throw new InvalidParameterException("project is null");
		}

		Collection<Statistics> todaysStats = statisticsDAO.retrieve(new Date());
		Collection<Statistics> projStats = new ArrayList<Statistics>();
		for (Statistics stats : todaysStats) {
			if (stats.getComponent().getProject().equals(project)) {
				projStats.add(stats);
			}
		}
		return projStats;
	}

	/**
	 * Return the earliest statistic for the given component.
	 */
	public Statistics getEarliestStatistics(Component component) {
		Collection<Statistics> compStats = getStatistics(component);
		Statistics earliestStatistics = null;
		Date minDate = null;
		for (Statistics stats : compStats) {
			if (minDate == null
					|| stats.getDateCalculated().getTime() < minDate.getTime()) {
				earliestStatistics = stats;
				minDate = stats.getDateCalculated();
			}
		}
		return earliestStatistics;
	}

	/**
	 * Return the total number of lines checked in, scaled by the starting
	 * point.
	 */
	public double getMinViolations(Component component) {
		Statistics earliestStats = this.getEarliestStatistics(component);
		Collection<Statistics> compStats = this.getStatistics(component);
		double minViolations = 0d;
		for (Statistics stats : compStats) {
			double scaledViolations = stats.getTotalViolations(false)
					- earliestStats.getTotalViolations(false);
			if (scaledViolations < minViolations) {
				minViolations = scaledViolations;
			}
		}
		return minViolations;
	}

	/**
	 * Return the max number of lines checked in, for all components.
	 */
	public double getMinViolations() {
		Collection<Component> components = this.getComponents();
		double minViolations = 0d;

		for (Component component : components) {
			double componentMinViolations = this.getMinViolations(component);
			if (componentMinViolations < minViolations) {
				minViolations = componentMinViolations;
			}
		}
		return minViolations;
	}

	/**
	 * Return the total number of lines checked in, scaled by the starting
	 * point.
	 */
	public double getMaxViolations(Component component) {
		Statistics earliestStats = this.getEarliestStatistics(component);
		Collection<Statistics> compStats = this.getStatistics(component);
		double maxViolations = 0d;
		for (Statistics stats : compStats) {
			double scaledViolations = stats.getTotalViolations(false)
					- earliestStats.getTotalViolations(false);
			if (scaledViolations > maxViolations) {
				maxViolations = scaledViolations;
			}
		}
		return maxViolations;
	}

	/**
	 * Return the max number of lines checked in, for all components.
	 */
	public double getMaxViolations() {
		Collection<Component> components = this.getComponents();
		double maxViolations = 0d;

		for (Component component : components) {
			double componentMaxViolations = this.getMaxViolations(component);
			if (componentMaxViolations > maxViolations) {
				maxViolations = componentMaxViolations;
			}
		}
		return maxViolations;
	}

	/**
	 * Return he total number violations, scaled by the starting point.
	 */
	public long getMaxNumberOfLines(Component component) {
		Statistics earliestStats = this.getEarliestStatistics(component);
		Collection<Statistics> compStats = this.getStatistics(component);
		long maxLOC = 0l;
		for (Statistics stats : compStats) {
			long scaledLOC = stats.getLinesOfCode()
					- earliestStats.getLinesOfCode();
			if (scaledLOC > maxLOC) {
				maxLOC = scaledLOC;
			}
		}
		return maxLOC;
	}

	/**
	 * Return the max number of lines checked in, for all components.
	 */
	public long getMaxNumberOfLines() {
		Collection<Component> components = this.getComponents();
		long maxLOC = 0l;
		for (Component component : components) {
			long componentMaxLOC = this.getMaxNumberOfLines(component);
			if (componentMaxLOC > maxLOC) {
				maxLOC = componentMaxLOC;
			}
		}
		return maxLOC;
	}

	/**
	 * Get the max total statistics for all in the statistics.
	 */
	public Double getMaxTotalViolations() {
		double maxTotalViolations = 0d;
		for (Statistics stats : statisticsDAO.retrieveAll()) {
			if (stats.getTotalViolations(true) > maxTotalViolations) {
				maxTotalViolations = stats.getTotalViolations(false);
			}
		}
		return maxTotalViolations;
	}

	/**
	 * Get the max total statistics for all in the statistics.
	 */
	public Double getMaxTotalLinesOfCode() {
		double maxTotalLinesOfCode = 0d;

		for (Statistics stats : statisticsDAO.retrieveAll()) {
			if (stats.getLinesOfCode() > maxTotalLinesOfCode) {
				maxTotalLinesOfCode = stats.getLinesOfCode();
			}
		}
		return maxTotalLinesOfCode;
	}

	public Collection<Date> getDates() {
		return this.statisticsDAO.retrieveDates();
	}

	/**
	 * Get the max daily total # of violations.
	 */
	public long getDailyMaxTotalLinesOfCode() {
		Collection<Date> dates = getDates();
		long maxTotalLOC = 0l;

		long initialLOC = 0l;
		for (Date date : dates) {
			initialLOC = this.getDailyTotalLinesOfCode(date);
			break;
		}
		dates = getDates();
		for (Date date : dates) {
			long dailyLOC = getDailyTotalLinesOfCode(date) - initialLOC;
			if (dailyLOC > maxTotalLOC) {
				maxTotalLOC = dailyLOC;
			}
		}
		return maxTotalLOC;
	}

	/**
	 * Get the min daily total # of violations.
	 */
	public double getDailyMinTotalViolations() {
		Collection<Date> dates = getDates();

		double initialViolations = 0d;
		long initialLOC = 0l;
		for (Date date : dates) {
			initialViolations = this.getDailyTotalViolations(date);
			break;
		}

		double minTotalViolations = 0d;
		for (Date date : dates) {
			double dailyViolations = getDailyTotalViolations(date)
					- initialViolations;
			if (dailyViolations < minTotalViolations) {
				minTotalViolations = dailyViolations;
			}
		}
		return minTotalViolations;
	}

	/**
	 * Get the max daily total # of violations.
	 */
	public double getDailyMaxTotalViolations() {
		Collection<Date> dates = getDates();

		double initialViolations = 0d;
		long initialLOC = 0l;
		for (Date date : dates) {
			initialViolations = this.getDailyTotalViolations(date);
			break;
		}

		double maxTotalViolations = 0d;
		for (Date date : dates) {
			double dailyViolations = getDailyTotalViolations(date)
					- initialViolations;
			if (dailyViolations > maxTotalViolations) {
				maxTotalViolations = dailyViolations;
			}
		}
		return maxTotalViolations;
	}

	/**
	 * Get the total # of violations across components for a given day.
	 */
	public double getDailyTotalViolations(Date date) {
		double totalViolations = 0d;

		Collection<Statistics> dailyStats = this.statisticsDAO.retrieve(date);
		for (Statistics stats : dailyStats) {
			totalViolations += stats.getTotalViolations(false);
		}

		return totalViolations;
	}

	/**
	 * Get the total # of violations across components for a given day.
	 */
	public long getDailyTotalLinesOfCode(Date date) {
		long totalLOC = 0l;
		Collection<Statistics> dailyStats = this.statisticsDAO.retrieve(date);
		for (Statistics stats : dailyStats) {
			if (DateUtil.datesAreEqual(stats.getDateCalculated(), date)) {
				totalLOC += stats.getLinesOfCode();
			}
		}

		return totalLOC;
	}

}
