"use strict";

const chalk = require("chalk");
const moment = require("moment");
const path = require("path");

const traverseTransformHistory = require("../util/traverse-transform-history");
const drawTransformTree = require("./draw-transform-tree");
const errors = require("../errors/errors");

function formatDate(date) {
	return chalk.dim(`[${moment(date).format("LTS")}]`);
}

function formatSuccessfulSourcePath(sourcePath) {
	return chalk.bgGreen.black(` ◆ ${sourcePath} `);
}

function formatFailedSourcePath(sourcePath) {
	return chalk.bgRed.white(` ◆ ${sourcePath} `);
}

function formatDuration(ms) {
	return chalk.bold(`${ms}ms`);
}

function formatTransformDisplayName(displayName) {
	return displayName;
}

function formatTransformPath(relativePath) {
	return chalk.dim(`[${relativePath}]`);
}

function formatSinkPath(sinkPath) {
	return chalk.bold(sinkPath);
}

function formatSuccessfulSourceStep(metadata) {
	return `${formatSuccessfulSourcePath(metadata.relativePath)} ${formatDate(metadata.sourcedDate)}`;
}

function formatSuccessfulTransformStep(metadata) {
	return `${formatDuration(metadata.stepDuration)} ${formatTransformDisplayName(metadata.step.displayName)} ${formatTransformPath(metadata.relativePath)}`;
}

function formatSuccessfulSinkStep(metadata) {
	return formatSinkPath(metadata.targetPath);
}

function formatMissingTransformStep(step) {
	return chalk.dim(`${chalk.bold("not run:")} ${step.displayName}`);
}

function formatMissingSinkStep() {
	return chalk.dim("(no output file produced)");
}

function formatFailedSourceStep(source) {
	return `${formatFailedSourcePath(source.relativePath)} ${formatDate(source.sourcedDate)}`;
}

function formatFailedTransformStepName(step, duration) {
	return chalk.red(`${formatDuration(duration)} ${step.displayName}`);
}

function formatErrorDetail(name, description) {
	return chalk.red(`${chalk.bold(`${name}:`)} ${description}`);
}

function formatFailedTransformStep(step, file, error, pipeline, duration) {
	if (error instanceof errors.SyntaxError) {
		let prefix = formatErrorPrefix("Syntax Error");
		let message = formatErrorMessage(error.error);

		return [
			formatFailedTransformStepName(step, duration),
			`${prefix}${message}`,
			formatErrorDetail("File", `${path.resolve(pipeline.basePath, file.metadata.relativePath)} ${file.metadata.isVirtual ? chalk.dim("(virtual)") : ""}`),
			formatErrorDetail("Location", `Line ${error.line}, column ${error.column}`),
			(file.metadata.isVirtual)
				? chalk.bgRed.white.bold("This file is a virtual file. That means that the syntax error probably originates from another earlier transform, and there may be a bug in that transform.")
				: ""
		].join("\n");
	} else {
		let prefix = formatErrorPrefix("Error");
		let message = formatErrorMessage(`An unexpected error occurred in the transform`);

		/* FIXME: */
		// if (error instanceof Error) {
		// 	console.log(chalk.red(error.stack));
		// } else {
		// 	console.log(chalk.red(util.inspect(error, {colors: true, depth: null})));
		// 	console.log(chalk.bgRed.white.bold("This error wasn't actually an error object - that's a bug in the transform! Here's a less precise stacktrace:"));
		// 	console.log(chalk.red((new Error()).stack));
		// }

		return [
			formatFailedTransformStepName(step),
			`${prefix}${message}`,
			error.stack
		].join("\n");
	}
}


// console.log(chalk.bgRed.white.bold(`A syntax error occurred in the transform '${transform.displayName}':`));
// console.log(chalk.red(`  ${chalk.bold("Error:")} ${error.error}`));
// console.log(chalk.red(`  ${chalk.bold("File:")} ${path.resolve(basePath, file.relativePath)} ${file.isVirtual ? chalk.dim("(virtual)") : ""}`));
// console.log(chalk.red(`  ${chalk.bold("Location:")} Line ${error.line}, column ${error.column}`));

function formatFailedSinkStep(step, error) {
	let prefix = formatErrorPrefix("Error");
	let message = formatErrorMessage(`Failed to write file to destination`);

	return [
		formatFailedTransformStepName(step),
		`${prefix}${message}`,
		error.stack
	].join("\n");
}

function formatErrorPrefix(text) {
	return chalk.bgRed.white(` ${text} `);
}

function formatErrorMessage(message) {
	return chalk.bgWhite.black(` ${message} `);
}

function formatCompletedItem(metadata) {
	let history = traverseTransformHistory(metadata);

	let sourceStep = formatSuccessfulSourceStep(history[0]);

	let transformSteps = history.slice(1, -1).map((metadata) => {
		return formatSuccessfulTransformStep(metadata);
	});

	let sinkStep = formatSuccessfulSinkStep(history[history.length - 1]);

	return drawTransformTree([sourceStep].concat(transformSteps).concat([sinkStep]));
}

function formatFailedItem(pipeline, {error, step, file, duration}) {
	let history = traverseTransformHistory(file.metadata);

	let sourceStep = formatFailedSourceStep(history[0]);

	let transformSteps, sinkStep;

	if (step.__buildStep === "sink") {
		transformSteps = history.slice(1, -1).map((metadata) => {
			return formatSuccessfulTransformStep(metadata);
		});

		sinkStep = formatFailedSinkStep(history[history.length - 1], error);
	} else {
		let failedIndex = pipeline.steps.indexOf(step);

		let successfulTransformSteps = history.slice(1, failedIndex).map((metadata) => {
			return formatSuccessfulTransformStep(metadata);
		});

		let failedTransformStep = formatFailedTransformStep(pipeline.steps[failedIndex], file, error, pipeline, duration);

		let missingTransformSteps = pipeline.steps.slice(failedIndex + 1, -1).map((step) => {
			return formatMissingTransformStep(step);
		});

		transformSteps = successfulTransformSteps.concat([failedTransformStep]).concat(missingTransformSteps);

		sinkStep = formatMissingSinkStep();
	}

	return drawTransformTree([sourceStep].concat(transformSteps).concat([sinkStep]));
}

module.exports = function runTask(task) {
	let pipeline = task();

	pipeline.on("error", (failure) => {
		console.log(formatFailedItem(pipeline, failure));

		if (failure.isFatal) {
			console.log(chalk.bgRed.white.bold("This error is fatal, and the build process will now exit."));
			/* FIXME: Do this more cleanly, with teardown and all. */
			process.exit(1);
		}

		console.log("");
	});

	pipeline.on("done", ({metadata, acknowledged}) => {
		console.log(formatCompletedItem(metadata));

		if (!acknowledged) {
			console.log(chalk.gray("(NOTE: The destination sink doesn't support acknowledgments, so the file may not actually be stored yet.)"));
		}

		console.log("");
	});

	pipeline.initialize();
};



// function reportError(transform, file, error, isFatal) {
// 	if (error instanceof errors.SyntaxError) {
// 		console.log(chalk.bgRed.white.bold(`A syntax error occurred in the transform '${transform.displayName}':`));
// 		console.log(chalk.red(`  ${chalk.bold("Error:")} ${error.error}`));
// 		console.log(chalk.red(`  ${chalk.bold("File:")} ${path.resolve(basePath, file.relativePath)} ${file.isVirtual ? chalk.dim("(virtual)") : ""}`));
// 		console.log(chalk.red(`  ${chalk.bold("Location:")} Line ${error.line}, column ${error.column}`));
//
//
// 	} else {
// 		console.log(chalk.bgRed.white.bold(`An error occurred in the transform '${transform.displayName}':`));
//
//
// 	}
//
// }

/* FIXME: Move this stuff out, use the acknowledgment event instead for detecting which tasks have finished; also don't forget to add a workaround for sinks that cannot acknowledge */



// console.log("===============");
// console.log(metadata);
// console.log(`Wrote ${targetFilePath}`);
