#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <SDL2/SDL.h>
#include <alsa/asoundlib.h>
#include <alsa/mixer.h>
#include <alsa/pcm.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>



#include "../include/types.h"
#include "../include/player.h"
#include "../include/audio/alsa.h"
#include "../include/gui/gui.h"


#define WAV_HEADER_LENGTH 44



void ProcessInput(SDL_Renderer* renderer, SDL_Texture* font, 
		AlsaData* alsa_data, PlayerData* player_data, TimelineData* timeline_data, 
		Widget* arr[256], uint8_t widgetNum) {

	int mouseX, mouseY = 0;
	
	//stop if a widget was already clicked
	bool clicked = false;
	static bool mouse_down = false;


	while (SDL_PollEvent(&(player_data->event)) && player_data->quit == false) {

		//quit window
		player_data->quit = (player_data->event.type == SDL_QUIT);

		//mouse input
		if (player_data->event.type == SDL_MOUSEBUTTONDOWN 
			|| player_data->event.type == SDL_MOUSEBUTTONUP
			|| player_data->event.type == SDL_MOUSEMOTION
			|| player_data->event.type == SDL_MOUSEWHEEL) {
		
			SDL_GetMouseState(&mouseX, &mouseY);
			if (mouseX < 0 || mouseY < 0) { return; }
			player_data->x = mouseX;
			player_data->y = mouseY;

			for (int i = widgetNum-1; i >= 0; i--) {
			
				//check if contains coordinate
				if (WidgetClicked(mouseX, mouseY, arr[i])) {
				
					clicked = true;
					arr[i]->hover = true;
					
					switch (player_data->event.type) {
					
						//action
						case SDL_MOUSEBUTTONDOWN:
							
							mouse_down = true;
							ButtonAction(mouseX, mouseY, arr[i], alsa_data, player_data, timeline_data, 0);
							player_data->widgetTarget = arr[i];
							break;

						//action if held down and slide
						case SDL_MOUSEMOTION:
							
							if (mouse_down == true && (arr[i]->sliderType != 0 || arr[i]->actionType == 16)) {
							
								ButtonAction(mouseX, mouseY, arr[i], alsa_data, player_data, timeline_data, 0);
							}
							break;
						
						//mouse up
						case SDL_MOUSEBUTTONUP:
							
							mouse_down = false;
							ButtonAction(mouseX, mouseY, arr[i], alsa_data, player_data, timeline_data, 2);
							break;
						
						//scroll
						case SDL_MOUSEWHEEL:
							
							ButtonAction(mouseX, mouseY, arr[i], alsa_data, player_data, timeline_data, 1);
							break;

						default:break;
					}
				} else {
					arr[i]->hover = false;

					if (player_data->event.type == SDL_MOUSEBUTTONUP) {
					
						mouse_down = false;
						//player_data->dragFile = NULL;
						timeline_data->sampleTarget = NULL;
					}
				}

				//check if list was clicked
				if (ListClicked(mouseX, mouseY, arr[i], player_data->w, player_data->h) && clicked == false) {
					
					switch (player_data->event.type) {
						
						//list clicked action
						case SDL_MOUSEBUTTONDOWN:
							ListAction(mouseX, mouseY, arr[i], player_data, timeline_data);
							break;
						//scroll
						case SDL_MOUSEWHEEL:
								
							if (arr[i]->list && arr[i]->actionType == 0 
									&& arr[i]->listOffset < arr[i]->listNum) {
						
								if ((player_data->event.wheel.y >= 0) ^ arr[i]->offsetY) {
								
									arr[i]->listOffset -= 1 * (arr[i]->listOffset > 0);
								} else {
									arr[i]->listOffset += 1 * (arr[i]->listOffset < arr[i]->listNum-1);
								}
							}
							break;
						default:break;
					}
				}
			}
		}

		//keyboard input
		if (player_data->widgetTarget != NULL) {
			
			switch (player_data->event.type) {
	
				//keyboard input to widget target
				case SDL_KEYDOWN:
					if (player_data->event.key.keysym.mod & (KMOD_SHIFT | KMOD_CAPS)) {
				
						KeyboardAction((char)(player_data->event.key.keysym.sym) - 32, 
								player_data->widgetTarget, alsa_data, player_data, timeline_data);
					} else {
						KeyboardAction((char)(player_data->event.key.keysym.sym), 
								player_data->widgetTarget, alsa_data, player_data, timeline_data);
					}
					break;	
				case SDL_KEYUP:
					break;	
				default:break;
			}
		}
	}
}



void PlayerLoopMain(SDL_Window* window, SDL_Renderer* renderer, 
		SDL_Texture* bgtexture, SDL_Texture* fonttexture, 
		Widget* widgetArr[256], uint8_t widgetNum, int DELAY,
		PlayerData* player_data, AlsaData* alsa_data, TimelineData* timeline_data) {

	//put everything below here into a loop for playlists
	while (!(player_data->quit)) {
	
		//loop for when player_data.file isnt playing
		while (player_data->file == NULL) {
			
			//draw window
			Draw(window, renderer, bgtexture, fonttexture, 
				widgetArr, widgetNum, 
				NULL, player_data, alsa_data, timeline_data);

			//user sdl input
			ProcessInput(renderer, fonttexture, alsa_data, player_data, timeline_data, widgetArr, widgetNum);
			if (player_data->quit) { break; }
			
			SDL_Delay(DELAY);
		}

		//setup audio file to be played
		//read player_data.file
		alsa_data->readfd = open(player_data->file, O_RDONLY);

		if (alsa_data->readfd < 0) {

			if (player_data->quit == false) {
				
				printf("ERROR: The file \"%s\" couldn't be read or doesn't exist.\n", player_data->file);
				player_data->file = NULL;
			}
			break;
		}
		
		//prepare audio device to play player_data.file
		//default params WavPreparePCM(alsa_data, 44100, 2, 2);
		WavPreparePCM(alsa_data, player_data->rate, 2, 2);
	
		//get player_data.file size relative to buffer size
		FILE* ftype;
		ftype = fopen(player_data->file, "r");
		fseek(ftype, 0, SEEK_END);
		player_data->fileSize = ftell(ftype) / alsa_data->buff_size;
		fseek(ftype, 0, SEEK_SET);
		fclose(ftype);
	
		//update widget with text for current file name and size
		char sizeStr[20];
		sprintf(sizeStr, "%d", player_data->fileSize);

		for (int i = 0; i < widgetNum; i++) {

			switch (widgetArr[i]->actionType) {
			
				case 0:UpdateWidgetText(player_data->file, widgetArr[i]);break;
				case 2:UpdateWidgetText(sizeStr, widgetArr[i]);		 break;
				default:break;
			}
		}

		//unhide other buttons
		for (int i = 1; i < widgetNum; i++) { widgetArr[i]->active = true; }

		//update background
		UpdateBackgroundImage(renderer, player_data->file, player_data);

		
		//main loop for playing audio
		do {
			//render audio for player
			if (player_data->playing == true) {
			
				//playing the actual audio from player_data.file
				
				//this works?????
				//read data and control volume
				int16_t val = 0;

				for (int i = player_data->fileIndex == 0 ? WAV_HEADER_LENGTH : 0; 
						i < alsa_data->buff_size; i += 2) {
			
					alsa_data->readval = pread(alsa_data->readfd, &val, sizeof(int16_t), 
							(player_data->fileIndex*alsa_data->buff_size) + i);
				
					val *= player_data->volume;
				
					alsa_data->buff[i+1] = val >> 8;
					alsa_data->buff[i] = val & 0xff;
				}
				
					
				//play audio
				if (alsa_data->readval > 0) {

					//volume scaling
					//Volume(&player_data, alsa_data);

					//play actual sound
					WavPlayBuffer(alsa_data);
						
					//move offset for next buffer data in player_data.file
					player_data->fileIndex++;
				} else {
					//printf("An error occured while reading from the player_data.file. Exiting.\n");
					break;
				}
			}
			
			//draw window
			Draw(window, renderer, bgtexture, fonttexture, 
					widgetArr, widgetNum, 
					player_data->file, player_data, alsa_data, timeline_data);
			//user sdl input
			ProcessInput(renderer, fonttexture, alsa_data, player_data, timeline_data, widgetArr, widgetNum);
			if (player_data->quit) { break; }

			SDL_Delay(DELAY);
		
		} while (alsa_data->readval > 0 && player_data->stop == false);

		
		//reset audio file player
		if (player_data->file != NULL && player_data->playing == true) {
			
			//close current player_data.file
			close(alsa_data->readfd);
			player_data->fileIndex = 0;
			player_data->fileSize = 0;
		
			//file ended, go to next file in list
			//this check also prevents file from
			//changing when its not supposed to
			if (player_data->stop == false) {
		
				player_data->fileTarget++;
			
				if (player_data->fileTarget < player_data->fileCount) {
			
					player_data->file = player_data->fileList[player_data->fileTarget];
				} else { 
					player_data->file = NULL;
				}
			}
			player_data->stop = false;
		}
	}
}

void DAWLoopMain(SDL_Window* window, SDL_Renderer* renderer, 
		SDL_Texture* bgtexture, SDL_Texture* fonttexture, 
		Widget* widgetArr[256], uint8_t widgetNum, int DELAY,
		PlayerData* player_data, AlsaData* alsa_data, TimelineData* timeline_data) {
	

	//init sound card and timeline before entering main loop

	//prepare audio device to play player_data.file
	//default params WavPreparePCM(alsa_data, 44100, 2, 2);
	WavPreparePCM(alsa_data, player_data->rate, 2, 2);
		
	//init memory for timeline audio data
	timeline_data->frame = malloc(sizeof(uint8_t)*alsa_data->buff_size);
	memset(timeline_data->frame, 0, alsa_data->buff_size);
	
	//60sec/bpm = seconds per beat
	timeline_data->spb = (double)(60.0/((double)timeline_data->bpm));
	double delayBeatCounters[16];
	
	gettimeofday(&(timeline_data->t0), 0);


	while (!(player_data->quit)) {
			
		//if unpaused
		if (player_data->playing == true) {
			
			//determine if sound should be playing or not
			bool audioPlaying = false;
			uint8_t numOfPlayingSamples = 0;
			
			//init audio data to 0 for every frame played	
			memset(timeline_data->frame, 0, alsa_data->buff_size);
			
			long usecPerBeat16 = (long)((timeline_data->spb/16.0)*1000000.0);

			//take audio date from samples and add to audio buffer
			for (int i = 0; i < timeline_data->sampleNum; i++) {
			
				Sample* sample = timeline_data->samples[i];

				//should a sample be playing right now
				if (timeline_data->currentBeat >= sample->beatOffset+sample->beatCropFront 
				&& timeline_data->currentBeat < sample->beatOffset+sample->beatLength-sample->beatCropBack) {
		
					if (sample->playing == true) {
							
						if (sample->dataOffset < sample->dataSize) { audioPlaying = true; }
				
						for (int j = 0; (j < alsa_data->buff_size) && ((sample->dataOffset+j+1) < sample->dataSize); j += 2) {
						
							uint16_t frameVal = (timeline_data->frame[j+1] << 8) | timeline_data->frame[j];
							uint16_t val = (sample->data[sample->dataOffset+j+1] << 8) | sample->data[sample->dataOffset+j];
							
							//add data
							frameVal += val;
							
							timeline_data->frame[j+1] = (frameVal >> 8);
							timeline_data->frame[j] = (frameVal & 0xff);
						}
						sample->dataOffset += alsa_data->buff_size;
					} else {
						double sampleCurrentTime = ((((double)(timeline_data->currentBeat-sample->beatOffset))/16.0) * timeline_data->spb);
						sample->dataOffset = (uint32_t)(((double)sample->dataSize) * (sampleCurrentTime/sample->seconds)) 
													+ (sample->cropOffset * alsa_data->buff_size);
						sample->playing = true;
					
						//need to do this to prevent playing data out of order
						if (sample->dataOffset % 2 == 1) { sample->dataOffset++; }
					}
					numOfPlayingSamples++;
				} else {
					sample->playing = false;
				}
			}

			
			if (audioPlaying == true) {	
			
				//move audio date from timeline to alsa data
				for (int i = 0; i < alsa_data->buff_size; i += 2) { 
				
					int16_t frameVal = (timeline_data->frame[i+1] << 8) | timeline_data->frame[i];
					
					//check for clipping
					if ((frameVal == 32767 || frameVal == -32768) && numOfPlayingSamples > 1) {
							
						if ((i == 0) || (i >= alsa_data->buff_size - 3)) { 
								
							frameVal = 0;

						//linear interpolation
						} else {
							frameVal = (((timeline_data->frame[i-1] << 8) | timeline_data->frame[i-2]) 
								+ ((timeline_data->frame[i+3] << 8) | timeline_data->frame[i+2])) / 2;
						}
					}

					//set master volume of program
					frameVal *= player_data->volume;

					//alsa_data->buff[i] = (char)timeline_data->frame[i];
					alsa_data->buff[i+1] = (frameVal >> 8);
					alsa_data->buff[i] = (frameVal & 0xff);
				}
				//play actual sound
				WavPlayBuffer(alsa_data);
			}

			
			//check time to progress according to BPM
			gettimeofday(&(timeline_data->t1), NULL);
			long elapsed = (timeline_data->t1.tv_sec-timeline_data->t0.tv_sec)*(1000000LL) + (timeline_data->t1.tv_usec-timeline_data->t0.tv_usec);

			if (elapsed >= usecPerBeat16) {
				
				timeline_data->currentBeat++;
				delayBeatCounters[0] += ((double)(elapsed-usecPerBeat16)/((double)usecPerBeat16));
				
				//timer correction
				for (int i = 0; i < 15; i++) {
				
					if (delayBeatCounters[i] >= 1.0) {
					
						timeline_data->currentBeat += (uint32_t)roundf(delayBeatCounters[i]);
						delayBeatCounters[i+1] += delayBeatCounters[i] - 1.0;
						delayBeatCounters[i] = 0.0;
					}
				}
			
				gettimeofday(&(timeline_data->t0), 0);
			}
		}
		
		//draw window
		Draw(window, renderer, bgtexture, fonttexture, 
				widgetArr, widgetNum, 
				NULL, player_data, alsa_data, timeline_data);
		//user sdl input
		ProcessInput(renderer, fonttexture, alsa_data, player_data, timeline_data, widgetArr, widgetNum);
		SDL_Delay(DELAY);
	}
}


int main(int argc, char* argv[]) {

	//data for soundcard
	AlsaData* alsa_data = InitAlsaData();
	
	//program data and init
	PlayerData* player_data = InitPlayerData();

	//daw timeline to contain audio track data, bpm, other things
	TimelineData* timeline_data = InitTimelineData(alsa_data);


	//print version
	SDL_version ver;
	SDL_GetVersion(&ver);
	printf("Kysela v1.0\n");
	printf("Made with ALSA v%s and ", SND_LIB_VERSION_STR);
	printf("SDL v%d.", ver.major);
	printf("%d.\n\n", ver.minor);

	
	//create sdl window and renderer
	const int WIDTH = player_data->w;
	const int HEIGHT = player_data->h;
	
	SDL_Window* window = NULL;
	SDL_Renderer* renderer = NULL;
	SDL_Surface* bgimage = NULL;
	SDL_Texture* bgtexture = NULL;
	SDL_Surface* font = NULL;
	SDL_Texture* fonttexture = NULL;
	SDL_Surface* icon = NULL;
	

	//initialize window	
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
	
		printf("ERROR: SDL couldn't be initialized. %s\n", SDL_GetError());
		return EXIT_FAILURE;
	} else {
		window = SDL_CreateWindow("test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
					  WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE);
		if (window == NULL) {
		
			printf("ERROR: SDL window couldn't be created. %s\n", SDL_GetError());
			return EXIT_FAILURE;
		} else {
			//create renderer
			renderer = SDL_CreateRenderer(window, -1, 0);
		
			if (renderer == NULL) {

				printf("ERROR: SDL renderer couldn't be created. %s\n", SDL_GetError());
				return EXIT_FAILURE;
			}
			SDL_RenderSetScale(renderer, 1, 1);
			
			//set window icon
			icon = SDL_LoadBMP("assets/icon.bmp");
			SDL_SetWindowIcon(window, icon);
			
			//do this for alpha color blending
			SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
		}
	}
	//we dont really need these anymore
	SDL_FreeSurface(bgimage);
	SDL_FreeSurface(font);

		
	//gui buttons and stuff
	//(fixed array for simplicity, prob won't need more than 256 anyway lol)
	Widget* widgetArr[256];
	uint8_t widgetNum = 0;
	for (int i = 0; i < 256; i++) { widgetArr[i] = NULL; }

	//load files and gui settings from config file
	if (argc > 1) { LoadFiles(argv[1], renderer, player_data, widgetArr, &widgetNum); }
	else { 		LoadFiles("kysela.config", renderer, player_data, widgetArr, &widgetNum); }
	SDL_SetWindowSize(window, player_data->w, player_data->h);	

	//ms delay from settings
	const int DELAY = player_data->delay;

	//copy list of files for searching
	for (int i = 0; i < player_data->fileCount; i++) {
		
		player_data->fileListCopy[i] = player_data->fileList[i];
		player_data->fileCountCopy++;
	}
			
	//load background image
	bgimage = SDL_LoadBMP(player_data->bgimage);
	bgtexture = SDL_CreateTextureFromSurface(renderer, bgimage);
			
	//text and font
	font = SDL_LoadBMP(player_data->font);
	fonttexture = SDL_CreateTextureFromSurface(renderer, font);

	//open pcm device
	if (OpenDevice(alsa_data)) { player_data->quit = true; }



	//see if entering player or daw mode
	bool dawFound = false;
	for (int i = 0; i < widgetNum; i++) {
		
		if (widgetArr[i]->actionType == BUTTON_TYPE_TIMELINE) { 
			
			printf("Press the '1' key for MOVE mode.\n");
			printf("Press the '2' key for CUT mode.\n");
			printf("Press the '3' key for COPY mode.\n");
			printf("Press the '4' key for DELETE mode.\n");
			dawFound = true; 
		}
	}
	
	
	if (dawFound) {
		//main loop for DAW
		DAWLoopMain(window, renderer, bgtexture, fonttexture, 
				widgetArr, widgetNum, DELAY,  
				player_data, alsa_data, timeline_data);
	} else {
		//main loop for music player	
		PlayerLoopMain(window, renderer, bgtexture, fonttexture, 
				widgetArr, widgetNum, DELAY,  
				player_data, alsa_data, timeline_data);
	}
	
	
	//cleanup gui
	DestroyWidgets(widgetArr, &widgetNum);

	//destroy window
	SDL_DestroyTexture(bgtexture);
	SDL_DestroyTexture(fonttexture);
	
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
	

	//clean up and close
	CloseDevice(alsa_data);
	
	DestroyAlsaData(alsa_data);
	DestroyPlayerData(player_data);
	DestroyTimelineData(timeline_data);

	printf("See you next time.\n");

	return 0;
}
