import { KarData, KarTimebase, KarTrack, KarTimebaseSection } from './types';
import { parseMidi } from 'midi-file';
import { clone } from './utils';
import debug from 'debug';

const debugTimebase = debug('timebase');
const debugTracks = debug('tracks');
const debugLyrics = debug('lyrics');

const isEventLyrics = event => (['text', 'lyrics'].includes(event.type) && !event.text.startsWith('@'));
const DEFAULT_EOL = 200;

export function parseKar(fileContent: Buffer): KarData {
	const midi = parseMidi(fileContent);
	const timebase: KarTimebase = getTimebase(midi);
	const rawTracks = getTracks(midi);
	rawTracks.forEach(track => applyTimebase(track, timebase));
	return {
		timebase: timebase,
		tracks: rawTracks,
	};
}

function applyTimebase(track: KarTrack, timebase: KarTimebase): void {
	const tmpTimebase: KarTimebase = clone(timebase);
	let time: KarTimebaseSection = tmpTimebase.shift();
	let baseUs: number = 0;
	for(const event of track) {
		while(event.ticks >= time.tickEnd) {
			baseUs += time.microSecondsPerTick * (time.tickEnd - time.tickStart);
			time = tmpTimebase.shift();
		}
		event.timeUs = baseUs + time.microSecondsPerTick * (event.ticks - time.tickStart);
		// console.error(`Mapped ${JSON.stringify({timeMillis: Math.round(event.timeUs/1000), type: event.type, text: event.text})}`)
	}
	debugTracks('Applied timebase to ', track);
}

function getTimebase(midi: any): KarTimebase {
	const tempoTrack: any = midi.tracks.filter(track => track.some(event => event.type == 'setTempo'))[0];
	debugTimebase('Tempo track is ', tempoTrack);
	const timebase: KarTimebase = [];
	let curr: number = 0;
	const tempoEvents: any = tempoTrack.filter(e => e.type === 'setTempo');
	for(let i = 0; i < tempoEvents.length; i++) {
		const tempoEvent: any = tempoEvents[i];
		const duration = (i+1<tempoEvents.length)
			? tempoEvents[i+1].deltaTime
			: Number.MAX_SAFE_INTEGER;
		timebase.push({
			tickStart: curr,
			tickEnd: curr + duration,
			microSecondsPerTick: tempoEvent.microsecondsPerBeat / midi.header.ticksPerBeat,
		});
		curr += duration;
		debugTimebase('Added timebase event', timebase[timebase.length - 1]);
	}
	return timebase;
}

function getTracks(midi: any, defaultEOL: number = DEFAULT_EOL): KarTrack[] {
	const response: KarTrack[] = [];
	debugTracks('All tracks from midi', midi.tracks);
	const tracksWithLyrics: any[] = midi.tracks
		.filter(events => events.some(isEventLyrics));
	debugTracks('Filtered tracks with lyric events', tracksWithLyrics);
	for(const rawTrack of tracksWithLyrics) {
		let timeCursor: number = 0;
		let track: KarTrack = [];
		for(const rawEvent of rawTrack) {
			if(isEventLyrics(rawEvent)) {
				const cleanText: string = rawEvent.text.replace(/[\/\\]/g, '');
				debugLyrics('Lyrics line: ', cleanText, rawEvent);
				if(['/', '\r', '\n', '\r\n'].includes(rawEvent.text)) {
					timeCursor += rawEvent.deltaTime;
					track.push({
						ticks: timeCursor,
						type: 'EndOfLine',
					});
				} else if(rawEvent.text.match(/^[\/\\]/)) {
					track.push({
						ticks: timeCursor + defaultEOL,
						type: 'EndOfLine',
					});
					timeCursor += rawEvent.deltaTime;
					track.push({
						ticks: timeCursor,
						type: 'Lyrics',
						text: cleanText,
					});
				} else if(rawEvent.text.match(/[\/\\]$/)) {
					timeCursor += rawEvent.deltaTime;
					track.push({
						ticks: timeCursor,
						type: 'Lyrics',
						text: cleanText,
					});
					track.push({
						ticks: timeCursor + defaultEOL,
						type: 'EndOfLine',
					});
				} else {
					timeCursor += rawEvent.deltaTime;
					track.push({
						ticks: timeCursor,
						type: 'Lyrics',
						text: cleanText,
					});
				}
			} else {
				timeCursor += rawEvent.deltaTime;
			}
		}
		if(track[track.length-1].type === 'Lyrics') {
			track.push({
				ticks: timeCursor + defaultEOL,
				type: 'EndOfLine',
			});
		}
		debugTracks('Finalized track: ', track);
		response.push(track);
	}
	return response;
}
