package org.pmdreports.manager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.InvalidParameterException;
import java.util.List;

import org.apache.log4j.Logger;
import org.pmdreports.dao.RulesetDAO;
import org.pmdreports.model.Component;
import org.pmdreports.model.Project;
import org.pmdreports.model.Ruleset;
import org.pmdreports.util.FileUtil;
import org.pmdreports.util.PMDReportsException;
import org.pmdreports.util.StringUtil;
import org.pmdreports.util.FileUtil.DirectoryCreationException;

/**
 * Manage all IO processes (i.e. creating directories, etc.).
 * 
 * @author Ben Northrop Sep 24, 2007
 */
public class FileManager {

	private final static Logger LOG = Logger.getLogger(FileManager.class);

	public static final String DELIMITER = ",";

	private final ConfigManager configManager = ConfigManager.getInstance();

	/**
	 * Count the number of lines of code in a given component.
	 */
	public Long calculateLinesOfCode(Component component) {
		File sourceDir = new File(getSourceDirectory(component));
		return calculateLinesOfCode(sourceDir);
	}

	/**
	 * Count the number of Java files a component has.
	 */
	public Long calculateNumberOfJavaFiles(Component component) {
		File sourceDir = new File(getSourceDirectory(component));
		return calculateNumberOfFiles(sourceDir);
	}

	/**
	 * Recursive method that counts the number of Java files a given directory
	 * has.
	 */
	private Long calculateNumberOfFiles(File file) {
		if (file.isDirectory()) {
			long numJavaFiles = 0l;
			String[] children = file.list();
			for (int i = 0; i < children.length; i++) {
				File childFile = new File(file, children[i]); // NOPMD
				numJavaFiles += calculateNumberOfFiles(childFile);
			}
			return numJavaFiles;
		} else {
			String fileName = file.getName();
			if (isJavaFile(fileName)) {
				return 1l;
			}
			return 0l;
		}
	}

	/**
	 * Determine whether a file has a .java extension.
	 */
	private boolean isJavaFile(String fileName) {
		return fileName.indexOf(".java", fileName.length() - 5) != -1;
	}

	/**
	 * Recursive method that counts the lines of code in a given directory.
	 */
	private Long calculateLinesOfCode(File file) {

		if (file.isDirectory()) {
			long numLines = 0l;
			String[] children = file.list();
			for (int i = 0; i < children.length; i++) {
				numLines += calculateLinesOfCode(new File(file, children[i])); // NOPMD
			}
			return numLines;
		}

		// Else is a file
		return countLines(file);
	}

	/**
	 * Count the number of lines of code in a java file.
	 */
	private Long countLines(File file) {
		String fileName = file.getName();
		if (!isJavaFile(fileName)) {
			return 0l;
		}

		FileReader fileReader = null;
		BufferedReader reader = null;
		long numLines = 0l;
		try {
			fileReader = new FileReader(file);
			reader = new BufferedReader(fileReader);
			while (reader.readLine() != null) {
				numLines++;
			}
		} catch (IOException ioe) {
			throw new PMDReportsException("IOException on " + file.getName(),
					ioe);
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
				if (fileReader != null) {
					fileReader.close();
				}
			} catch (IOException ioe) {
				throw new PMDReportsException("IOException on "
						+ file.getName(), ioe);
			}
		}
		LOG.debug(file.getName() + ": " + numLines);
		return numLines;
	}

	/**
	 * Get the source directory for a given component.
	 */
	public String getSourceDirectory(Component component) {
		if (component == null) {
			throw new InvalidParameterException("component is null!");
		}
		Project project = component.getProject();
		if (project == null) {
			throw new InvalidParameterException("project null of component: "
					+ component.getName());
		}
		String sourceDirectory = project.getSrcRoot() + "\\"
				+ component.getPath();
		File file = new File(sourceDirectory);
		if (!file.exists()) {
			throw new PMDReportsException("Component (" + component.getName()
					+ ") source directory does not exist: "
					+ component.getPath());
		}
		return sourceDirectory;
	}

	/**
	 * Get the output directory for a given component. If it does not exist,
	 * create it.
	 */
	private String getOutputDirectory(Component component,
			boolean suppressOutDir) {
		Project project = component.getProject();
		String projectPath = suppressOutDir ? project.getName() : configManager
				.getOutputDir()
				+ project.getName();
		File projectDir = new File(projectPath);
		if (!projectDir.exists() && !suppressOutDir) {
			LOG.debug("Creating project output directory: " + projectDir);
			projectDir.mkdir();
		}

		String componentPath = projectPath + "/" + component.getName();
		File componentDir = new File(componentPath);
		if (!componentDir.exists()) {
			LOG.debug("Creating component output directory: " + componentDir);
			componentDir.mkdir();
		}
		return componentPath;
	}

	/**
	 * Get the path to the PMD file for a given component.
	 */
	public String getPmdXmlFilePath(Component component) {
		String componentPath = getOutputDirectory(component, false);
		return componentPath + "\\pmd.xml";
	}

	/**
	 * Get the path to the PMD file for a given component.
	 */
	public String getPmdCsvFilePath(Component component) {
		String componentPath = getOutputDirectory(component, false);
		return componentPath + "\\pmd.txt";
	}

	/**
	 * Get the path to the PMD HTML report.
	 */
	public String getPmdHtmlFilePath(Component component, Ruleset ruleset) {
		String componentPath = getOutputDirectory(component, false);
		return componentPath + "\\pmd" + ruleset.getName() + ".html";
	}

	/**
	 * Get the path to the PMD HTML report.
	 * 
	 * @param suppressOut -
	 *            suppress the out directory, for reports that are in the local
	 *            directory and want the relative path to the component.
	 */
	public String getPmdHtmlFilePath(Component component, Ruleset ruleset,
			boolean suppressOut) {
		String componentPath = getOutputDirectory(component, suppressOut);
		return componentPath + "\\pmd" + ruleset.getName() + ".html";
	}

	/**
	 * Read in the first line of a file that contains PMD statistics from a
	 * given program.
	 */
	public List<String> readPmdData(Component component) {
		BufferedReader bufferedReader = null;
		String csvPath = "";
		try {
			csvPath = this.getPmdCsvFilePath(component);
			FileInputStream fileInputStream = new FileInputStream(csvPath);
			InputStreamReader inputStreamReader = new InputStreamReader(
					fileInputStream);
			bufferedReader = new BufferedReader(inputStreamReader);
			String line = bufferedReader.readLine();
			return StringUtil.getSplitList(line, FileManager.DELIMITER);
		} catch (IOException e) {
			throw new PMDReportsException("Error reading file: " + csvPath, e);
		} finally {
			try {
				if (bufferedReader != null) {
					bufferedReader.close();
				}
			} catch (IOException ioe) {
				throw new PMDReportsException("Error closing file: " + csvPath,
						ioe);
			}
		}

	}

	/**
	 * Create an output directory for all generated reports, etc.
	 */
	public void createOutputDirectory() {
		try {
			FileUtil.createDirectory(configManager.getOutputDir());
		} catch (DirectoryCreationException dce) { // NOPMD
			// do nothing, means it already exists, which is ok.
		}
	}

	/**
	 * Generate the file.
	 */
	public void generatePmdXslFile() {
		RulesetDAO dao = new RulesetDAO(configManager.getRulesetsFile());
		List<Ruleset> rulesets = dao.retrieveRulesets();
		String xsl = createPmdXslFile(rulesets);
		writeFile(configManager.getPmdXslFile(), xsl);
	}

	/**
	 * Get the contents of the file, given all the PMD rulesets.
	 */
	@SuppressWarnings("PMD.ConsecutiveLiteralAppends")
	private String createPmdXslFile(List<Ruleset> rulesets) {
		StringBuffer xsl = new StringBuffer(3000);
		xsl.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		xsl.append("<xsl:stylesheet ");
		xsl.append(" version=\"1.0\" ");
		xsl.append(" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n");
		xsl.append("<xsl:output method=\"text\" />\n");
		xsl.append("<xsl:template match=\"pmd\">\n");
		xsl.append("<xsl:value-of select=\"count(//violation)\"/>");
		for (Ruleset ruleset : rulesets) {
			xsl
					.append(",<xsl:value-of select=\"count(//violation[@ruleset = '");
			xsl.append(ruleset.getDescription());
			xsl.append("'])\"/>");
		}
		xsl.append('\n');
		xsl.append("</xsl:template>\n");
		xsl.append("</xsl:stylesheet>");
		return xsl.toString();
	}

	/**
	 * Write out the XSL file.
	 */
	public void writeFile(String fileName, String fileContents) {
		try {
			FileOutputStream fos = new FileOutputStream(fileName);
			PrintWriter pw = new PrintWriter(fos);
			pw.println(fileContents);
			pw.close();
			fos.close();
		} catch (IOException ioe) {
			throw new PMDReportsException("IOException when storing!", ioe);
		}
	}
}
