package org.pmdreports.manager;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import net.sourceforge.pmd.PMD;

import org.apache.log4j.Logger;
import org.pmdreports.dao.ProjectDAO;
import org.pmdreports.dao.RulesetDAO;
import org.pmdreports.model.Component;
import org.pmdreports.model.Project;
import org.pmdreports.model.Ruleset;
import org.pmdreports.model.Statistics;
import org.pmdreports.util.ListUtil;
import org.pmdreports.util.PMDReportsException;
import org.pmdreports.util.StringUtil;

/**
 * Generate the XSL file that takes PMD XML and converts to a comma delimited
 * file, depending on the rules that are being configured.
 * 
 * @author Ben Northrop Sep 24, 2007
 */
public class PMDManager {

	// DATA MEMBERS
	private final static Logger LOG = Logger.getLogger(PMDManager.class);

	private final FileManager fileManager = new FileManager();

	private final ConfigManager configManager = ConfigManager.getInstance();

	private final static String XML_FORMAT = "xml";

	private final static String HTML_FORMAT = "html";

	private final static String REPORT_FILE_OPTION = "-reportfile";

	private final List<Ruleset> rulesets;

	// CONSTRUCTOR
	/**
	 * Default constructor.
	 */
	public PMDManager() {
		RulesetDAO dao = new RulesetDAO(configManager.getRulesetsFile());
		rulesets = dao.retrieveRulesets();
	}

	// METHODS
	/**
	 * Run PMD given the configuration (projects and components).
	 */
	public void runPMD() {
		fileManager.createOutputDirectory();
		fileManager.generatePmdXslFile();

		ProjectDAO dao = new ProjectDAO(configManager.getProjectsFile());
		String strRulesets = buildRulesetList();

		List<Project> projects = dao.retrieveProjectConfig();
		if (projects == null) {
			throw new PMDReportsException("No projects configured "
					+ configManager.getProjectsFile());
		}

		for (Project project : dao.retrieveProjectConfig()) {
			for (Component component : project.getComponents()) {
				String sourceDir = fileManager.getSourceDirectory(component);

				// Run PMD and generate XML report
				String xmlFile = fileManager.getPmdXmlFilePath(component);
				String[] params = new String[] { sourceDir, XML_FORMAT, // NOPMD
						strRulesets, REPORT_FILE_OPTION, xmlFile };
				logPmdRun(params);
				PMD.main(params);

				// Run PMD and generate HTML AbstractReport
				for (Ruleset ruleset : rulesets) {
					String htmlFile = fileManager.getPmdHtmlFilePath(component,
							ruleset);
					String strRuleset = StringUtil.isEmpty(ruleset
							.getFileName()) ? ruleset.getName() : ruleset
							.getFileName();

					params = new String[] { sourceDir, HTML_FORMAT, strRuleset, // NOPMD
							REPORT_FILE_OPTION, htmlFile };
					logPmdRun(params);
					PMD.main(params);
				}

				// Convert the XML to CSV, and then persist
				transformXml(component);
				storePmdStats(component);
			}
		}
	}

	/**
	 * Print out the params of passed to the pmd.
	 */
	private void logPmdRun(String[] params) {
		StringBuffer sb = new StringBuffer(200);
		sb.append("Running PMD:");
		for (String param : params) {
			sb.append(' ').append(param);
		}
		sb.append('\n');
		LOG.info(sb.toString());
	}

	/**
	 * Transform the pmd.xml file to a CSV text file.
	 */
	private void transformXml(Component component)
			throws TransformerFactoryConfigurationError {
		String csvFile = fileManager.getPmdCsvFilePath(component);
		String xmlFile = fileManager.getPmdXmlFilePath(component);
		try {
			String key = "javax.xml.transform.TransformerFactory";
			String value = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
			Properties props = System.getProperties();
			props.put(key, value);
			System.setProperties(props);

			// Instantiate the TransformerFactory, and use it along with
			// a StreamSource
			// XSL stylesheet to create a Transformer.
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer(new StreamSource(
					configManager.getPmdXslFile()));

			// Perform the transformation from a StreamSource to a
			// StreamResult;
			transformer.transform(new StreamSource(xmlFile), new StreamResult(
					new FileOutputStream(csvFile)));
		} catch (TransformerConfigurationException tce) {
			throw new PMDReportsException("Could not transform XML!", tce);
		} catch (FileNotFoundException fnfe) {
			throw new PMDReportsException("Could not find file!", fnfe);
		} catch (TransformerException te) {
			throw new PMDReportsException("TransformerException transforming: "
					+ xmlFile + ": " + te, te);
		}
	}

	/**
	 * Build a list of rulesets given the rulesets.xml config file.
	 */
	private String buildRulesetList() {

		StringBuffer rulesetString = new StringBuffer(500);
		for (Ruleset ruleset : rulesets) {
			if (!StringUtil.isEmpty(rulesetString.toString())) {
				rulesetString.append(FileManager.DELIMITER);
			}
			if (StringUtil.isEmpty(ruleset.getFileName())) {
				rulesetString.append(ruleset.getName());
			} else {
				rulesetString.append(ruleset.getFileName());
			}
		}
		return rulesetString.toString();
	}

	private void storePmdStats(Component component) {

		List<String> pmdData = fileManager.readPmdData(component);

		Statistics result = new Statistics(component);

		int cnt = 1;
		for (Ruleset ruleset : rulesets) {
			result.addStatistic(ruleset, ListUtil.getValue(pmdData, cnt));
			cnt++;
		}

		Long linesOfCode = fileManager.calculateLinesOfCode(component);
		result.setLinesOfCode(linesOfCode);

		Long numberOfJavaFiles = fileManager
				.calculateNumberOfJavaFiles(component);
		result.setNumberOfJavaFiles(numberOfJavaFiles);

		result.setDateCalculated(new Date());

		StatisticsManager componentManager = StatisticsManager.getInstance();
		componentManager.addComponentStatistics(result);
		LOG.debug(result.toString());
	}
}
