/*
 *   This file is part of open_agb_firm
 *   Copyright (C) 2021 derrek, profi200
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "error_codes.h"
#include "fs.h"
#include "util.h"
#include "arm11/drivers/hid.h"
#include "arm11/console.h"
#include "arm11/fmt.h"
#include "drivers/gfx.h"
#include "arm11/acf.h"
#include "arm11/atp.h"

#define screenClean() memset(consoleGet()->frameBuffer, 0, CWIDTH*CHEIGHT*sizeof(uint16_t))

// Notes on these settings:
// MAX_ENT_BUF_SIZE should be big enough to hold the average file/dir name length * MAX_DIR_ENTRIES.
#define MAX_ENT_BUF_SIZE  (1024u * 196) // 196 KiB.
#define MAX_DIR_ENTRIES   (1000u)
#define DIR_READ_BLOCKS   (10u)

//#define SCREEN_COLS       (53u - 1) // - 1 because the console inserts a newline after the last line otherwise.
//#define SCREEN_ROWS       (24u)
#define SCREEN_COLS			19
#define SCREEN_ROWS			14u

#define LINENO_TO_Y(y)		15+(y)*15
#define CWIDTH                  320u
#define CHEIGHT                 240u
#define CLEFTMARGIN		20u
#define CLINELIMIT		282u

#define ENT_TYPE_FILE  (0)
#define ENT_TYPE_DIR   (1)

typedef struct
{
	u32 num;                       // Total number of entries.
	char entBuf[MAX_ENT_BUF_SIZE]; // Format: char entryType; char name[X]; // null terminated.
	char *ptrs[MAX_DIR_ENTRIES];   // For fast sorting.
} DirList;

int dlistCompare(const void *a, const void *b)
{
	const char *entA = *(char**)a;
	const char *entB = *(char**)b;

	// Compare the entry type. Dirs have priority over files.
	if(*entA != *entB) return (int)*entB - *entA;

	// Compare the string.
	int res;
	do
	{
		res = *++entA - *++entB;
	} while(res == 0 && *entA != '\0' && *entB != '\0');

	return res;
}

static Result scanDir(const char *const path, DirList *const dList, const char *const filter, const char *match, int *index)
{
	FILINFO *const fis = (FILINFO*)malloc(sizeof(FILINFO) * DIR_READ_BLOCKS);
	if(fis == NULL) return RES_OUT_OF_MEM;

	dList->num = 0;

	Result res;
	DHandle dh;
	if((res = fOpenDir(&dh, path)) == RES_OK)
	{
		u32 read;           // Number of entries read by fReadDir().
		u32 numEntries = 0; // Total number of processed entries.
		u32 entBufPos = 0;  // Entry buffer position/number of bytes used.
		const u32 filterLen = strlen(filter);
		do
		{
			if((res = fReadDir(dh, fis, DIR_READ_BLOCKS, &read)) != RES_OK) break;
			read = (read <= MAX_DIR_ENTRIES - numEntries ? read : MAX_DIR_ENTRIES - numEntries);

			for(u32 i = 0; i < read; i++)
			{
				const char entType = (fis[i].fattrib & AM_DIR ? ENT_TYPE_DIR : ENT_TYPE_FILE);
				const u32 nameLen = strlen(fis[i].fname);
				if(entType == ENT_TYPE_FILE)
				{
					if(nameLen <= filterLen || strcmp(filter, fis[i].fname + nameLen - filterLen) != 0)
						continue;
				}

				// nameLen does not include the entry type and NULL termination.
				if(entBufPos + nameLen + 2 > MAX_ENT_BUF_SIZE) goto scanEnd;

				char *const entry = &dList->entBuf[entBufPos];
				*entry = entType;
				safeStrcpy(&entry[1], fis[i].fname, 256);
				dList->ptrs[numEntries++] = entry;
				entBufPos += nameLen + 2;
			}
		} while(read == DIR_READ_BLOCKS);

scanEnd:
		dList->num = numEntries;

		fCloseDir(dh);
	}

	free(fis);

	qsort(dList->ptrs, dList->num, sizeof(char*), dlistCompare);

	if( match != NULL )
	{
		for( u32 i=0; i < dList->num; ++i )
		{
			if( strcmp(match, &dList->ptrs[i][1]) == 0 )
			{
				*index = i;
				break;
			}
		}
	}

	return res;
}

static atp_text_t folder_help[] = {
	"操 作 说 明",
	"~    ~    ~    ~    ~    ~    ~",
	"蓝色　　　　　　　　　　　　文件夹",
	"白色　　　　　　　　　　　游戏文件",
	"~    ~    ~    ~    ~    ~    ~",
	"Ａ键　　　　　查看文件夹或运行游戏",
	"Ｂ键　　　　　　　　　　上层文件夹",
	"Ｘ键　　　　　　　　　　　　金手指",
	"Ｙ键　　　　　　　　　　　游戏设置",
	"ＳＴＡＲＴ　　　　　　　　查看说明",
	"ＳＥＬＥＣＴ　　　　　　　系统设置"
};

static atp_error_t display_folder( atp_callerdata_t data, atp_counter_t index, atp_itemcfg_t *config )
{
	DirList *const dList = (DirList*)data;
	u8 type = *dList->ptrs[index];
	if( type == ENT_TYPE_DIR )
		config->text_color = ATP_COLOR_BLUE;
	config->text = &dList->ptrs[index][1];
	config->value = index;
	return ATP_SUCCESS;
}

static atp_error_t display_empty( atp_callerdata_t, atp_counter_t, atp_linecfg_t *config )
{
	config->text = "没有合适的文件";
	config->text_align = ATP_PLACEMENT_CENTER;
	config->text_color = ATP_COLOR_RED;
	return ATP_SUCCESS;
}

static atp_error_t display_help( atp_callerdata_t table, atp_counter_t index, atp_linecfg_t *config )
{
	atp_text_t *list = (atp_text_t*)table;
	config->text_align = ATP_PLACEMENT_CENTER;
	config->text_color = index == 0 ? ATP_COLOR_MAGENTA : ATP_COLOR_LIGHT;
	config->text = list[index];
	return ATP_SUCCESS;
}

static void help_page( atp_text_t *wording, atp_counter_t length )
{
	atp_tips( NULL, "返回：按A/B" );
	atp_show( length, display_help, (atp_callerdata_t)wording );
}

#define DIRBUFFSIZE 512
#define DIRUP 	{ \
					char *tmpPathPtr = curDir + pathLen; \
					while(*--tmpPathPtr != '/'); \
					if(*(tmpPathPtr - 1) == ':') tmpPathPtr++; \
					*tmpPathPtr = '\0'; \
					scan = 1; \
					upFrom = tmpPathPtr + 1; \
				}
Result browseFiles(const char *const basePath, char selected[512])
{
	if(basePath == NULL || selected == NULL) return RES_INVALID_ARG;
	// TODO: Check if the base path is empty.

	char *curDir = (char*)malloc(DIRBUFFSIZE);
	if(curDir == NULL) return RES_OUT_OF_MEM;
	safeStrcpy(curDir, basePath, 512);

	DirList *const dList = (DirList*)malloc(sizeof(DirList));
	if(dList == NULL) return RES_OUT_OF_MEM;

	Result res;
	if((res = scanDir(curDir, dList, ".gba", NULL, NULL)) != RES_OK) goto end;
	
	const char *upFrom = NULL;
	int selecting = 0;
	while( 1 )
	{
		atp_itemval_t value;
		atp_boolean_t scan = 0;
		atp_error_t error;
		const u32 count = dList->num;

		u32 pathLen = strlen(curDir);

		if( count > 0 )
		{
			atp_tips( NULL, "指引：按START" );
			error = atp_select( curDir, count, display_folder, NULL, (atp_callerdata_t)dList, selecting, 0, &value );
		}
		else error = atp_show( 1, display_empty, NULL );


		if( error == ATP_POWER_OFF )
		{
			res = error;
			break;
		}
		else if( error == ATP_SUCCESS )
		{// 进入目录或文件
			if( count == 0 )
			{
				DIRUP;
			}
			else 
			{
				const char *fname = &dList->ptrs[value][1];
				u32 namelen = strlen( fname ) + 1;
				if( namelen + pathLen > DIRBUFFSIZE-1 )
				{
					res = RES_OUT_OF_MEM;
					break;
				}
				curDir[pathLen++] = '/';
				safeStrcpy( curDir+pathLen, fname, DIRBUFFSIZE - namelen );
				if( *dList->ptrs[value] == ENT_TYPE_FILE )
				{
					safeStrcpy( selected, curDir, 512 );
					break;
				}
				else 
				{
					scan = 1;
					selecting = 0;
					upFrom = NULL;
				}
			}
		}
		else if( error == ATP_NO_ACTION )
		{// 上层目录
			if( strcmp(curDir, FS_DRIVE_NAMES) == 0 )
			{
				atp_tips("没有上层目录", NULL);
				help_page( folder_help, sizeof(folder_help)/sizeof(atp_text_t) );
				atp_tips("", NULL);
			}
			else DIRUP;
		}

		if( scan )
		{
			if(RES_OK != (res = scanDir(curDir, dList, ".gba", upFrom, &selecting)) )
				break;
		}
	}

end:
	free(dList);
	free(curDir);

	// Clear screen.
	screenClean();

	return res;
}
