package net.sourceforge.cobertura.dsl;

import net.sourceforge.cobertura.check.CoverageThreshold;
import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler;
import net.sourceforge.cobertura.instrument.CoberturaFile;
import net.sourceforge.cobertura.util.FileFinder;
import net.sourceforge.cobertura.util.RegexUtil;
import org.apache.oro.text.regex.Pattern;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/*
 * Cobertura - http://cobertura.sourceforge.net/
 *
 * 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
 */

/**
 * Arguments builder - provides a DSL to build cobertura Arguments
 */
public class ArgumentsBuilder {
	// Visible for testing
	static final String DEFAULT_ENCODING = "UTF-8";
	static final double DEFAULT_THRESHOLD = 0.;
	static final boolean DEFAULT_CALCULATE_METHOD_COMPLEXITY = false;
	static final boolean DEFAULT_FAIL_ON_ERROR = false;
	static final boolean DEFAULT_IGNORE_TRIVIAL = false;
	static final boolean DEFAULT_THREADSAFE_RIGOROUS = false;

	private Arguments arguments;

	private String baseDirectory;
	private File dataFile;
	private File destinationDirectory;
	private File commandsFile;
	private List<CodeSource> sources;

	private Collection ignoreRegexes;
	private Collection<Pattern> ignoreBranchesRegexes;
	private Collection<Pattern> classPatternIncludeClassesRegexes;
	private Collection<Pattern> classPatternExcludeClassesRegexes;
	private boolean calculateMethodComplexity;
	private boolean failOnError;
	private boolean ignoreTrivial;
	private boolean threadsafeRigorous;

	private String encoding;

	private Set<CoverageThreshold> minimumCoverageThresholds;
	private double classLineThreshold;
	private double classBranchThreshold;
	private double packageLineThreshold;
	private double packageBranchThreshold;
	private double totalLineThreshold;
	private double totalBranchThreshold;

	private Set<CoberturaFile> filesToInstrument;
	private Set<File> filesToMerge;
	private Set<String> ignoreMethodAnnotations;
	private Set<String> ignoreClassAnnotations;

	public ArgumentsBuilder() {
		initVariables();
	}

	public ArgumentsBuilder setBaseDirectory(String baseDir) {
		baseDirectory = baseDir;
		return this;
	}

	public ArgumentsBuilder setDataFile(String dataFile) {
		this.dataFile = new File(dataFile);
		return this;
	}

	public ArgumentsBuilder setDestinationDirectory(String destinationDir) {
		this.destinationDirectory = new File(destinationDir);
		return this;
	}

	public ArgumentsBuilder setCommandsFile(String commandsFile) {
		this.commandsFile = new File(commandsFile);
		return this;
	}

	public ArgumentsBuilder addIgnoreRegex(String regex) {
		RegexUtil.addRegex(ignoreRegexes, regex);;
		return this;
	}

	public ArgumentsBuilder addIgnoreBranchRegex(String regex) {
		RegexUtil.addRegex(ignoreBranchesRegexes, regex);
		return this;
	}

	public ArgumentsBuilder addIgnoreMethodAnnotation(
			String ignoreMethodAnnotation) {
		ignoreMethodAnnotations.add(ignoreMethodAnnotation);
		return this;
	}

	public ArgumentsBuilder addIgnoreClassAnnotation(
			String ignoreClassAnnotation) {
		ignoreClassAnnotations.add(ignoreClassAnnotation);
		return this;
	}

	public ArgumentsBuilder addExcludeClassesRegex(String regex) {
		RegexUtil.addRegex(classPatternExcludeClassesRegexes, regex);
		return this;
	}

	public ArgumentsBuilder addIncludeClassesRegex(String regex) {
		RegexUtil.addRegex(classPatternIncludeClassesRegexes, regex);
		return this;
	}

	public ArgumentsBuilder calculateMethodComplexity(boolean calculateMethodComplexity) {
		this.calculateMethodComplexity = calculateMethodComplexity;
		return this;
	}

	public ArgumentsBuilder failOnError(boolean failOnError) {
		this.failOnError = failOnError;
		return this;
	}

	public ArgumentsBuilder ignoreTrivial(boolean ignoreTrivial) {
		this.ignoreTrivial = ignoreTrivial;
		return this;
	}

	public ArgumentsBuilder threadsafeRigorous(boolean threadsafeRigorous) {
		this.threadsafeRigorous = threadsafeRigorous;
		return this;
	}

	public ArgumentsBuilder listOfFilesToInstrument(String listFileName) {
		String baseDir = getBaseDirectory();
		try {
			File file = new File(listFileName);
			FileReader fileReader = new FileReader(file);
			BufferedReader bufferedReader = new BufferedReader(fileReader);
			StringBuffer stringBuffer = new StringBuffer();
			String line;
			while ((line = bufferedReader.readLine()) != null) {
				line = line.replace(baseDir, "");
				filesToInstrument.add(new CoberturaFile(baseDir, line));
			}
			fileReader.close();
		} catch (IOException e) {
		  e.printStackTrace();
		}
		return this;
	}

	public ArgumentsBuilder setEncoding(String encoding) {
		this.encoding = encoding;
		return this;
	}

	public ArgumentsBuilder addMinimumCoverageRates(String regex,
			double branchPercentage, double linePercentage) {
		minimumCoverageThresholds.add(new CoverageThreshold(regex,
				branchPercentage, linePercentage));
		return this;
	}

	public ArgumentsBuilder setClassBranchCoverageThreshold(
			double coverageThreshold) {
		classBranchThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder setClassLineCoverageThreshold(
			double coverageThreshold) {
		classLineThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder setPackageBranchCoverageThreshold(
			double coverageThreshold) {
		packageBranchThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder setPackageLineCoverageThreshold(
			double coverageThreshold) {
		packageLineThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder setTotalBranchCoverageThreshold(
			double coverageThreshold) {
		totalBranchThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder setTotalLineCoverageThreshold(
			double coverageThreshold) {
		totalLineThreshold = inRange(coverageThreshold);
		return this;
	}

	public ArgumentsBuilder addFileToInstrument(String file) {
		String baseDir = getBaseDirectory();
		if ( baseDir != null ) {
			file = file.replace(baseDir, "");
		}

		filesToInstrument.add(new CoberturaFile(baseDir, file));
		return this;
	}

	public ArgumentsBuilder addFileToMerge(String file) {
		filesToMerge.add(new File(file));
		return this;
	}

	public ArgumentsBuilder addSources(String sourcePath, boolean isDirectory) {
		if (this.sources == null) {
			this.sources = new ArrayList<CodeSource>();
		}
		this.sources.add(new CodeSource(isDirectory, sourcePath));
		return this;
	}

	public Arguments build() {
		FileFinder sources = new FileFinder();

		if (this.sources != null) {
			for (CodeSource codeSource : this.sources) {
				if (codeSource.isDirectory()) {
					sources.addSourceDirectory(codeSource.getPath());
				} else {
					sources.addSourceFile(getBaseDirectory(),
							codeSource.getPath());
				}
			}
		}

		return new Arguments(baseDirectory, dataFile, destinationDirectory,
				commandsFile, ignoreRegexes, ignoreBranchesRegexes,
				classPatternIncludeClassesRegexes,
				classPatternExcludeClassesRegexes, calculateMethodComplexity,
				failOnError, ignoreTrivial,
				threadsafeRigorous, encoding, minimumCoverageThresholds,
				classLineThreshold, classBranchThreshold, packageLineThreshold,
				packageBranchThreshold, totalLineThreshold,
				totalBranchThreshold, filesToInstrument, filesToMerge,
				ignoreMethodAnnotations, ignoreClassAnnotations, sources);
	}

	private double inRange(double coverageRate) {
		if ((coverageRate >= 0.) && (coverageRate <= 1.)) {
			return coverageRate;
		}
		throw new IllegalArgumentException(String.format(
				"The value %s is invalid.  Rates must be between 0.0 and 1.0",
				coverageRate));
	}

	private void initVariables() {
		dataFile = CoverageDataFileHandler.getDefaultDataFile();
//		baseDirectory = new File(".");
		ignoreRegexes = new Vector();
		ignoreBranchesRegexes = new Vector<Pattern>();
		ignoreMethodAnnotations = new HashSet<String>();
		ignoreClassAnnotations = new HashSet<String>();
		classPatternExcludeClassesRegexes = new HashSet<Pattern>();
		classPatternIncludeClassesRegexes = new HashSet<Pattern>();
		filesToInstrument = new HashSet<CoberturaFile>();
		filesToMerge = new HashSet<File>();
		minimumCoverageThresholds = new HashSet<CoverageThreshold>();

		// previous rule was: default threshold is 0.5 for all
		// if a threshold is specified, the others are defaulted to 0
		classBranchThreshold = DEFAULT_THRESHOLD;
		classLineThreshold = DEFAULT_THRESHOLD;
		packageBranchThreshold = DEFAULT_THRESHOLD;
		packageLineThreshold = DEFAULT_THRESHOLD;
		totalBranchThreshold = DEFAULT_THRESHOLD;
		totalLineThreshold = DEFAULT_THRESHOLD;

		calculateMethodComplexity = DEFAULT_CALCULATE_METHOD_COMPLEXITY;
		failOnError = DEFAULT_FAIL_ON_ERROR;
		ignoreTrivial = DEFAULT_IGNORE_TRIVIAL;
		threadsafeRigorous = DEFAULT_THREADSAFE_RIGOROUS;
		encoding = DEFAULT_ENCODING;
	}

	private String getBaseDirectory() {
		return baseDirectory;
	}

	private static class CodeSource {
		private boolean directory;
		private String path;

		private CodeSource(boolean directory, String path) {
			this.directory = directory;
			this.path = path;
		}

		public boolean isDirectory() {
			return directory;
		}

		public String getPath() {
			return path;
		}
	}
}
