package funkin;

import funkin.Section.SwagSection;
import funkin.noteStuff.NoteBasic.NoteData;
import funkin.play.PlayState;
import haxe.Json;
import lime.utils.Assets;

typedef SwagSong =
{
	var song:String;
	var notes:FunnyNotes;
	var difficulties:Array<String>;
	var noteMap:Map<String, Array<SwagSection>>;
	var bpm:Float;
	var needsVoices:Bool;
	var voiceList:Array<String>;
	var speed:FunnySpeed;
	var speedMap:Map<String, Float>;

	var player1:String;
	var player2:String;
	var validScore:Bool;
	var extraNotes:Map<String, Array<SwagSection>>;
}

typedef FunnySpeed =
{
	var ?easy:Float;
	var ?normal:Float;
	var ?hard:Float;
}

typedef FunnyNotes =
{
	var ?easy:Array<SwagSection>;
	var ?normal:Array<SwagSection>;
	var ?hard:Array<SwagSection>;
}

class SongLoad
{
	public static var curDiff:String = 'normal';
	public static var curNotes:Array<SwagSection>;
	public static var songData:SwagSong;

	public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong
	{
		var rawJson:String = null;
		try
		{
			rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim();
		}
		catch (e)
		{
			trace('Failed to load song data: ${e}');
			rawJson = null;
		}

		if (rawJson == null)
		{
			return null;
		}

		while (!rawJson.endsWith("}"))
		{
			rawJson = rawJson.substr(0, rawJson.length - 1);
		}

		return parseJSONshit(rawJson);
	}

	public static function getSong(?diff:String):Array<SwagSection>
	{
		if (diff == null)
			diff = SongLoad.curDiff;

		var songShit:Array<SwagSection> = [];

		// THIS IS OVERWRITTEN, WILL BE DEPRECTATED AND REPLACED SOOOOON
		if (songData != null)
		{
			switch (diff)
			{
				case 'easy':
					songShit = songData.notes.easy;
				case 'normal':
					songShit = songData.notes.normal;
				case 'hard':
					songShit = songData.notes.hard;
			}
		}

		checkAndCreateNotemap(curDiff);

		songShit = songData.noteMap[diff];

		return songShit;
	}

	public static function checkAndCreateNotemap(diff:String):Void
	{
		if (songData == null || songData.noteMap == null)
			return;
		if (songData.noteMap[diff] == null)
			songData.noteMap[diff] = [];
	}

	public static function getSpeed(?diff:String):Float
	{
		if (PlayState.instance != null && PlayState.instance.currentChart != null)
		{
			return getSpeed_NEW(diff);
		}

		if (diff == null)
			diff = SongLoad.curDiff;

		var speedShit:Float = 1;

		// all this shit is overridden by the thing that loads it from speedMap Map object!!!
		// replace and delete later!
		switch (diff)
		{
			case 'easy':
				speedShit = songData.speed.easy;
			case 'normal':
				speedShit = songData.speed.normal;
			case 'hard':
				speedShit = songData.speed.hard;
		}

		if (songData.speedMap[diff] == null)
			songData.speedMap[diff] = 1;

		speedShit = songData.speedMap[diff];

		return speedShit;
	}

	public static function getSpeed_NEW(?diff:String):Float
	{
		if (PlayState.instance == null || PlayState.instance.currentChart == null || PlayState.instance.currentChart.scrollSpeed == 0.0)
			return 1.0;

		return PlayState.instance.currentChart.scrollSpeed;
	}

	public static function getDefaultSwagSong():SwagSong
	{
		return {
			song: 'Test',
			notes: {easy: [], normal: [], hard: []},
			difficulties: ["easy", "normal", "hard"],
			noteMap: new Map(),
			speedMap: new Map(),
			bpm: 150,
			needsVoices: true,
			player1: 'bf',
			player2: 'dad',
			speed: {
				easy: 1,
				normal: 1,
				hard: 1
			},
			validScore: false,
			voiceList: ["BF", "BF-pixel"],
			extraNotes: []
		};
	}

	public static function getDefaultNoteData():NoteData
	{
		return new NoteData();
	}

	/**
	 *	Casts the an array to NOTE data (for LOADING shit from json usually)
	 */
	public static function castArrayToNoteData(noteStuff:Array<SwagSection>)
	{
		if (noteStuff == null)
			return;

		for (sectionIndex => section in noteStuff)
		{
			if (section == null || section.sectionNotes == null)
				continue;
			for (noteIndex => noteDataArray in section.sectionNotes)
			{
				var arrayDipshit:Array<Dynamic> = cast noteDataArray; // crackhead

				if (arrayDipshit != null) // array isnt null, that means it loaded it as an array and needs to be manually parsed?
				{
					// at this point noteStuff[sectionIndex].sectionNotes[noteIndex] is an array because of the cast from the first line in this function
					// so this line right here turns it back into the NoteData typedef type because of another bastard cast
					noteStuff[sectionIndex].sectionNotes[noteIndex] = cast SongLoad.getDefaultNoteData(); // turn it from an array (because of the cast), back to noteData? yeah that works

					noteStuff[sectionIndex].sectionNotes[noteIndex].strumTime = arrayDipshit[0];
					noteStuff[sectionIndex].sectionNotes[noteIndex].noteData = arrayDipshit[1];
					noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2];
					if (arrayDipshit.length > 3)
					{
						noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[3];
					}
				}
				else if (noteDataArray != null)
				{
					// array is NULL, so it checks if noteDataArray (doesnt exactly NEED to be an 'array' is also null or not.)
					// At this point it should be an OBJECT that can be easily casted!!!

					noteStuff[sectionIndex].sectionNotes[noteIndex] = cast noteDataArray;
				}
				else
					throw "shit brokey"; // i actually dont know how throw works lol
			}
		}
	}

	/**
	 * Cast notedata to ARRAY (usually used for level SAVING)
	 */
	public static function castNoteDataToArray(noteStuff:Array<SwagSection>)
	{
		if (noteStuff == null)
			return;

		for (sectionIndex => section in noteStuff)
		{
			for (noteIndex => noteTypeDefShit in section.sectionNotes)
			{
				var dipshitArray:Array<Dynamic> = [
					noteTypeDefShit.strumTime,
					noteTypeDefShit.noteData,
					noteTypeDefShit.sustainLength,
					noteTypeDefShit.noteKind
				];

				noteStuff[sectionIndex].sectionNotes[noteIndex] = cast dipshitArray;
			}
		}
	}

	public static function castNoteDataToNoteData(noteStuff:Array<SwagSection>)
	{
		if (noteStuff == null)
			return;

		for (sectionIndex => section in noteStuff)
		{
			for (noteIndex => noteTypedefShit in section.sectionNotes)
			{
				trace(noteTypedefShit);
				noteStuff[sectionIndex].sectionNotes[noteIndex] = noteTypedefShit;
			}
		}
	}

	public static function parseJSONshit(rawJson:String):SwagSong
	{
		var songParsed:Dynamic;
		try
		{
			songParsed = Json.parse(rawJson);
		}
		catch (e)
		{
			FlxG.log.warn("Error parsing JSON: " + e.message);
			trace("Error parsing JSON: " + e.message);
			return null;
		}

		var swagShit:SwagSong = cast songParsed.song;
		swagShit.difficulties = []; // reset it to default before load
		swagShit.noteMap = new Map();
		swagShit.speedMap = new Map();
		for (diff in Reflect.fields(songParsed.song.notes))
		{
			swagShit.difficulties.push(diff);
			swagShit.noteMap[diff] = cast Reflect.field(songParsed.song.notes, diff);

			castArrayToNoteData(swagShit.noteMap[diff]);

			// castNoteDataToNoteData(swagShit.noteMap[diff]);

			/* 
				switch (diff)
				{
					case "easy":
						castArrayToNoteData(swagShit.notes.hard);

					case "normal":
						castArrayToNoteData(swagShit.notes.normal);
					case "hard":
						castArrayToNoteData(swagShit.notes.hard);
				}
			 */
		}

		for (diff in swagShit.difficulties)
		{
			swagShit.speedMap[diff] = cast Reflect.field(songParsed.song.speed, diff);
		}

		// trace(swagShit.noteMap.toString());
		// trace(swagShit.speedMap.toString());
		// trace('that was just notemap string lol');

		swagShit.validScore = true;

		trace("SONG SHIT ABOUTTA WEEK AGOOO");
		for (field in Reflect.fields(Json.parse(rawJson).song.speed))
		{
			// swagShit.speed[field] = Reflect.field(Json.parse(rawJson).song.speed, field);
			// swagShit.notes[field] = Reflect.field(Json.parse(rawJson).song.notes, field);
			// trace(swagShit.notes[field]);
		}

		// swagShit.notes = cast Json.parse(rawJson).song.notes[SongLoad.curDiff]; // by default uses

		trace('THAT SHIT WAS JUST THE NORMAL NOTES!!!');
		songData = swagShit;
		// curNotes = songData.notes.get('normal');

		return swagShit;
	}
}