/*
 * Cobertura - http://cobertura.sourceforge.net/
 *
 * Copyright (C) 2003 jcoverage ltd.
 * Copyright (C) 2005 Mark Doliner
 * Copyright (C) 2005 Nathan Wilson
 * Copyright (C) 2009 Charlie Squires
 *
 * Cobertura 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 2 of the License,
 * or (at your option) any later version.
 *
 * Cobertura 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 Cobertura; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

package net.sourceforge.cobertura.check;

import java.io.File;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import net.sourceforge.cobertura.coveragedata.ClassData;
import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler;
import net.sourceforge.cobertura.coveragedata.ProjectData;
import net.sourceforge.cobertura.util.Header;

import org.apache.log4j.Logger;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;

public class Main
{

	private static final Logger logger = Logger.getLogger(Main.class);

	final Perl5Matcher pm = new Perl5Matcher();

	final Perl5Compiler pc = new Perl5Compiler();

	/**
	 * The default CoverageRate needed for a class to pass the check.
	 */
	CoverageRate minimumCoverageRate;

	/**
	 * The keys of this map contain regular expression Patterns that
	 * match against classes.  The values of this map contain
	 * CoverageRate objects that specify the minimum coverage rates
	 * needed for a class that matches the pattern.
	 */
	Map minimumCoverageRates = new HashMap();

	/**
	 * The keys of this map contain package names. The values of this 
	 * map contain PackageCoverage objects that track the line and
	 * branch coverage values for a package.
	 */
	Map packageCoverageMap = new HashMap();

	double inRangeAndDivideByOneHundred(String coverageRateAsPercentage)
	{
		return inRangeAndDivideByOneHundred(Integer.valueOf(
				coverageRateAsPercentage).intValue());
	}

	double inRangeAndDivideByOneHundred(int coverageRateAsPercentage)
	{
		if ((coverageRateAsPercentage >= 0)
				&& (coverageRateAsPercentage <= 100))
		{
			return (double)coverageRateAsPercentage / 100;
		}
		throw new IllegalArgumentException("The value "
				+ coverageRateAsPercentage
				+ "% is invalid.  Percentages must be between 0 and 100.");
	}

	void setMinimumCoverageRate(String minimumCoverageRate)
			throws MalformedPatternException
	{
		StringTokenizer tokenizer = new StringTokenizer(minimumCoverageRate,
				":");
		this.minimumCoverageRates.put(pc.compile(tokenizer.nextToken()),
				new CoverageRate(inRangeAndDivideByOneHundred(tokenizer
						.nextToken()), inRangeAndDivideByOneHundred(tokenizer
						.nextToken())));
	}

	/**
	 * This method returns the CoverageRate object that
	 * applies to the given class.  If checks if there is a
	 * pattern that matches the class name, and returns that
	 * if it finds one.  Otherwise it uses the global minimum
	 * rates that were passed in.
	 */
	CoverageRate findMinimumCoverageRate(String classname)
	{
		Iterator iter = this.minimumCoverageRates.entrySet().iterator();
		while (iter.hasNext())
		{
			Map.Entry entry = (Map.Entry)iter.next();

			if (pm.matches(classname, (Pattern)entry.getKey()))
			{
				return (CoverageRate)entry.getValue();
			}
		}
		return this.minimumCoverageRate;
	}

	public Main(String[] args) throws MalformedPatternException
	{
		int exitStatus = 0;

		Header.print(System.out);

		File dataFile = CoverageDataFileHandler.getDefaultDataFile();
		double branchCoverageRate = -1.0;
		double lineCoverageRate = -1.0;
		double packageBranchCoverageRate = -1.0;
		double packageLineCoverageRate = -1.0;
		double totalBranchCoverageRate = -1.0;
		double totalLineCoverageRate = -1.0;

		for (int i = 0; i < args.length; i++)
		{
			if (args[i].equals("--branch"))
			{
				branchCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
			else if (args[i].equals("--datafile"))
			{
				dataFile = new File(args[++i]);
			}
			else if (args[i].equals("--line"))
			{
				lineCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
			else if (args[i].equals("--regex"))
			{
				setMinimumCoverageRate(args[++i]);
			}
			else if (args[i].equals("--packagebranch"))
			{
				packageBranchCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
			else if (args[i].equals("--packageline"))
			{
				packageLineCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
			else if (args[i].equals("--totalbranch"))
			{
				totalBranchCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
			else if (args[i].equals("--totalline"))
			{
				totalLineCoverageRate = inRangeAndDivideByOneHundred(args[++i]);
			}
		}

		ProjectData projectData = CoverageDataFileHandler
				.loadCoverageData(dataFile);

		if (projectData == null)
		{
			System.err.println("Error: Unable to read from data file "
					+ dataFile.getAbsolutePath());
			System.exit(1);
		}

		// If they didn't specify any thresholds, then use some defaults
		if ((branchCoverageRate == -1.0) && (lineCoverageRate == -1.0)
				&& (packageLineCoverageRate == -1.0)
				&& (packageBranchCoverageRate == -1.0)
				&& (totalLineCoverageRate == -1.0)
				&& (totalBranchCoverageRate == -1.0)
				&& (this.minimumCoverageRates.size() == 0))
		{
			branchCoverageRate = 0.5;
			lineCoverageRate = 0.5;
			packageBranchCoverageRate = 0.5;
			packageLineCoverageRate = 0.5;
			totalBranchCoverageRate = 0.5;
			totalLineCoverageRate = 0.5;
		}
		// If they specified one or more thresholds, default everything else to 0
		else
		{
			if (branchCoverageRate == -1.0)
				branchCoverageRate = 0.0;
			if (lineCoverageRate == -1.0)
				lineCoverageRate = 0.0;
			if (packageLineCoverageRate == -1.0)
				packageLineCoverageRate = 0.0;
			if (packageBranchCoverageRate == -1.0)
				packageBranchCoverageRate = 0.0;
			if (totalLineCoverageRate == -1.0)
				totalLineCoverageRate = 0.0;
			if (totalBranchCoverageRate == -1.0)
				totalBranchCoverageRate = 0.0;
		}

		this.minimumCoverageRate = new CoverageRate(lineCoverageRate,
				branchCoverageRate);

		double totalLines = 0;
		double totalLinesCovered = 0;
		double totalBranches = 0;
		double totalBranchesCovered = 0;

		Iterator iter = projectData.getClasses().iterator();
		while (iter.hasNext())
		{
			ClassData classData = (ClassData)iter.next();
			CoverageRate coverageRate = findMinimumCoverageRate(classData
					.getName());

			if (totalBranchCoverageRate > 0.0)
			{
				totalBranches += classData.getNumberOfValidBranches();
				totalBranchesCovered += classData.getNumberOfCoveredBranches();
			}

			if (totalLineCoverageRate > 0.0)
			{
				totalLines += classData.getNumberOfValidLines();
				totalLinesCovered += classData.getNumberOfCoveredLines();
			}

			PackageCoverage packageCoverage = getPackageCoverage(classData
					.getPackageName());
			if (packageBranchCoverageRate > 0.0)
			{
				packageCoverage.addBranchCount(classData
						.getNumberOfValidBranches());
				packageCoverage.addBranchCoverage(classData
						.getNumberOfCoveredBranches());
			}

			if (packageLineCoverageRate > 0.0)
			{
				packageCoverage.addLineCount(classData.getNumberOfValidLines());
				packageCoverage.addLineCoverage(classData
						.getNumberOfCoveredLines());
			}

			logger.debug("Class " + classData.getName()
					+ ", line coverage rate: "
					+ percentage(classData.getLineCoverageRate())
					+ "%, branch coverage rate: "
					+ percentage(classData.getBranchCoverageRate()) + "%");

			if (classData.getBranchCoverageRate() < coverageRate
					.getBranchCoverageRate())
			{
				System.err.println(classData.getName()
						+ " failed check. Branch coverage rate of "
						+ percentage(classData.getBranchCoverageRate())
						+ "% is below "
						+ percentage(coverageRate.getBranchCoverageRate())
						+ "%");
				exitStatus |= 2;
			}

			if (classData.getLineCoverageRate() < coverageRate
					.getLineCoverageRate())
			{
				System.err.println(classData.getName()
						+ " failed check. Line coverage rate of "
						+ percentage(classData.getLineCoverageRate())
						+ "% is below "
						+ percentage(coverageRate.getLineCoverageRate()) + "%");
				exitStatus |= 4;
			}
		}

		exitStatus |= checkPackageCoverageLevels(packageBranchCoverageRate,
				packageLineCoverageRate);

		// Check the rates for the overall project
		if ((totalBranches > 0)
				&& (totalBranchCoverageRate > (totalBranchesCovered / totalBranches)))
		{
			System.err
					.println("Project failed check. "
							+ "Total branch coverage rate of "
							+ percentage(totalBranchesCovered / totalBranches)
							+ "% is below "
							+ percentage(totalBranchCoverageRate) + "%");
			exitStatus |= 8;
		}

		if ((totalLines > 0)
				&& (totalLineCoverageRate > (totalLinesCovered / totalLines)))
		{
			System.err.println("Project failed check. "
					+ "Total line coverage rate of "
					+ percentage(totalLinesCovered / totalLines)
					+ "% is below " + percentage(totalLineCoverageRate) + "%");
			exitStatus |= 16;
		}

		System.exit(exitStatus);
	}

	private PackageCoverage getPackageCoverage(String packageName)
	{
		PackageCoverage packageCoverage = (PackageCoverage)packageCoverageMap
				.get(packageName);
		if (packageCoverage == null)
		{
			packageCoverage = new PackageCoverage();
			packageCoverageMap.put(packageName, packageCoverage);
		}
		return packageCoverage;
	}

	private int checkPackageCoverageLevels(double packageBranchCoverageRate,
			double packageLineCoverageRate)
	{
		int exitStatus = 0;
		Iterator iter = packageCoverageMap.entrySet().iterator();
		while (iter.hasNext())
		{
			Map.Entry entry = (Map.Entry)iter.next();
			String packageName = (String)entry.getKey();
			PackageCoverage packageCoverage = (PackageCoverage)entry.getValue();

			exitStatus |= checkPackageCoverage(packageBranchCoverageRate,
					packageLineCoverageRate, packageName, packageCoverage);
		}
		return exitStatus;
	}

	private int checkPackageCoverage(double packageBranchCoverageRate,
			double packageLineCoverageRate, String packageName,
			PackageCoverage packageCoverage)
	{
		int exitStatus = 0;
		double branchCoverage = packageCoverage.getBranchCoverage()
				/ packageCoverage.getBranchCount();
		if ((packageCoverage.getBranchCount() > 0)
				&& (packageBranchCoverageRate > branchCoverage))
		{
			System.err.println("Package " + packageName
					+ " failed check. Package branch coverage rate of "
					+ percentage(branchCoverage) + "% is below "
					+ percentage(packageBranchCoverageRate) + "%");
			exitStatus |= 32;
		}

		double lineCoverage = packageCoverage.getLineCoverage()
				/ packageCoverage.getLineCount();
		if ((packageCoverage.getLineCount() > 0)
				&& (packageLineCoverageRate > lineCoverage))
		{
			System.err.println("Package " + packageName
					+ " failed check. Package line coverage rate of "
					+ percentage(lineCoverage) + "% is below "
					+ percentage(packageLineCoverageRate) + "%");
			exitStatus |= 64;
		}

		return exitStatus;
	}

	private String percentage(double coverateRate)
	{
		BigDecimal decimal = new BigDecimal(coverateRate * 100);
		return decimal.setScale(1, BigDecimal.ROUND_DOWN).toString();
	}

	public static void main(String[] args) throws MalformedPatternException
	{
		new Main(args);
	}

}
