#!/usr/bin/env node
import { asyncExists, asyncReadFile, asyncWriteFile, clone, usToAss } from './utils';
import stringify from 'ass-stringify';
import { v4 as uuid } from 'uuid';
import cli from 'commander';
import { KarData, Options, KarTrack, KarEvent } from './types';
import { parseKar as _parseKar } from './kar';
const ass = require('./assTemplate');
const pjson = require('../package.json');

function generateASSLine(line: KarEvent[], sectionId:string, options: Options) {
	const offset: number = options.offset || 0;
	const syncType: string = options.progressive ? 'kf' : 'k';

	const dialogue = clone(ass.dialogue);
	const comment = clone(ass.dialogue);
	// keep times as µs
	const lineStart = line[0].timeUs + 1e4 * (offset - ass.previewTime);
	const lineStartComment = line[0].timeUs + 1e4 * offset;
	const lineEnd = line[line.length-1].timeUs + 1e4 * (offset + ass.postviewTime);
	const correction = lineStart < 0 ? lineStart : 0;

	dialogue.value.Style = sectionId;
	dialogue.value.Start = usToAss(lineStart - correction);
	dialogue.value.End = usToAss(lineEnd - correction);
	dialogue.value.Text = writeLineAsAss(line, syncType);
	dialogue.value.Effect = 'fx';

	comment.value.Style = sectionId;
	comment.value.Start = usToAss(lineStartComment - correction);
	comment.value.End = usToAss(lineEnd - correction);
	comment.value.Text = writeLineAsAss(line, syncType, true);
	comment.value.Effect = 'karaoke';
	comment.key = 'Comment';

	return {
		dialogue,
		comment
	};
}

export function parseKar(fileContent: Buffer): KarData { // Proxy Method
	return _parseKar(fileContent);
}

export function convertKarStringToAss(fileContent: Buffer, options: Options): string {
	return convertKarToAss(parseKar(fileContent), options);
}

export function convertKarToAss(kar: KarData, options: Options): string {
	const defaultStyle = ass.styles.body.filter(style => style.key == 'Style')[0];
	const dialogues = [];
	const comments = [];

	const styles = clone(ass.styles);
	const script = clone(ass.dialogue);
	script.value.Effect = ass.scriptFX;
	script.value.Text = ass.script;
	script.key = 'Comment';
	comments.push(script);

	for(const track of kar.tracks) {
		const sectionId: string = uuid().replace(/-/g, '').substring(0,16);

		const sectionStyle = clone(defaultStyle);
		sectionStyle.value.Name = sectionId;
		styles.body.push(sectionStyle);

		for(const line of splitLines(track)) {
			const ASSLines = generateASSLine(line, sectionId, options);
			comments.push(clone(ASSLines.comment));
			dialogues.push(clone(ASSLines.dialogue));
		}
	}

	const scriptInfo = clone(ass.scriptInfo);
	scriptInfo.body[0].value = scriptInfo.body[0].value.replace('%version', pjson.version);
	scriptInfo.body[1].value = scriptInfo.body[1].value.replace('%options', JSON.stringify(options));
	const events = clone(ass.events);
	events.body = events.body.concat(comments, dialogues);

	return stringify([scriptInfo, styles, events]);
}

function splitLines(track: KarTrack): KarEvent[][] {
	const lines: KarEvent[][] = [];
	let tempLine: KarEvent[] = [];
	for(const event of track) {
		if(event.type === 'EndOfLine' && tempLine.length > 0) {
			tempLine.push(event);
			lines.push(tempLine);
			tempLine = [];
		} else if(event.type === 'Lyrics') {
			tempLine.push(event);
		// } else {
		// 	console.debug(`Ignored ${JSON.stringify(event)}`)
		}
	}
	return lines;
}
function writeLineAsAss(line: KarEvent[], syncType: string, comment?:boolean) {
	if(line.length == 0) return '';
	const realPreview = Math.max(Math.min(ass.previewTime, line[0].timeUs * 1e-4), 0);
	const resArray: string[] = [];
	if (!comment) resArray.push(`{\\k${realPreview}\\fad(300,200)}`);
	for(let i = 0; i < line.length-1; i++) {
		// console.error(`At ${usToAss(line[i].timeUs)} for ${Math.round(1e-4 * (line[i+1].timeUs-line[i].timeUs))} cs: '${line[i].text}'`);
		resArray.push(`${line[i].text.startsWith(' ') ? ' ' : ''}{\\${syncType}${Math.round(1e-4 * (line[i+1].timeUs-line[i].timeUs))}}${line[i].text.trim()}${(line[i].text.endsWith(' ') ? ' ' : '')}`);
	}
	return resArray.join('');
}

async function mainCLI() {
	const argv = parseArgs();
	if(!argv.in) 
		throw '--in argument is required';

	const karFile: string = argv.in;
	const correctionString: string = argv.correction;
	const outFile: string = argv.out;

	const correction = (correctionString && !isNaN(+correctionString))
		? +correctionString : 0;
	
	const options: Options = {
		offset: correction,
		progressive: argv.progressive || false,
	};

	if(!await asyncExists(karFile))
		throw `File ${karFile} does not exist`;

	const fileContent = await asyncReadFile(karFile);

	const kar: KarData = parseKar(fileContent);

	const assString = convertKarToAss(kar, options);

	if(!outFile) {
		return assString;
	} else {
		await asyncWriteFile(outFile, assString);
		return '';
	}
}

function parseArgs() {
	const argv = process.argv.filter(e => e !== '--');
	return cli
		.command('kar-to-ass')
		.description('Converts midi/kar files to ass format')
		.version(pjson.version)
		.option('-i, --in <karFile> (required)', 'the file to read data from')
		.option('-o, --out <assFile>', 'if set, saves the converted data there instead of printing it on stdout')
		.option('-c, --correction <cs>', 'offsets said amount of cs on each line', 0)
		.option('-p, --progressive', 'use kf instead of k ')
		.parse(argv);
}


if (require.main === module) mainCLI()
	.then(data => console.log(data))
	.catch(err => console.error(err));

