#include <PalmOS.h>
#include "Preferences.h"
#include "PanelList.h"
#include "Keys.h"


#pragma warn_a5_access on	//programming without globals is fun :-)

void prvSetFormExtendedFlags(FormPtr fp,UInt32 flagIndex,UInt16 value){
	
	UInt32 x;
	
	if((errNone == FtrGet('psys',32,&x)) && (x == 1)){
		
		x=*((UInt32*)((UInt32)fp - 4));
		
		x = ((x >> 24) & 0x00FF)|
			((x >>  8) & 0xFF00)|
			((x & 0xFF00) <<  8)|
			((x & 0x00FF) << 24);
		
		((UInt16*)x)[flagIndex] = value;
	}
}

UInt32 prvUiGetConst(UInt32 which){
	
	Coord width,height;
	
	WinGetDisplayExtent(&width,&height);
	
	switch(which){
		
		case CONST_NUM_ROWS:
			
			return (height - TOP_MARGIN) / LINE_HEIGHT;
		
		
		case CONST_NUM_COLS:
			
			return (width > 160) ? 3 : 2;
		
		
		case CONST_FORM_WIDTH:
			
			return width;
		
		
		case CONST_FORM_HEIGHT:
			
			return height;
		
		
		case CONST_COL_WIDTH:
			
			height = prvUiGetConst(CONST_NUM_COLS);
			return (width - 7 - ((height + 1) * LEFT_MARGIN)) / height;
			
		default:
			
			return 0;
	}
}

UInt32 UiNumLinesNeeded(PanelList* list){
	
	UInt32 nc = prvUiGetConst(CONST_NUM_COLS);
	UInt32 i,num,lines = 0;
	
	for(i = 0; i < PanelListGetNumCategories(list); i++){
		
		lines++;
		PanelListGetNthCategory(list,i,NULL,&num);
		
		lines += (num + nc - 1) / nc;
	}
		
	return lines;
}

UInt32 UiItemLine(PanelList* list,UInt32 cat,UInt32 item){
	
	UInt32 nc = prvUiGetConst(CONST_NUM_COLS);
	UInt32 i,num,lines = 0;
	
	for(i = 0; i < cat; i++){
		
		lines++;
		PanelListGetNthCategory(list,i,NULL,&num);
		
		lines += (num + nc - 1) / nc;
	}
	
	lines++; //my cat header
	
	lines += (item) / nc;
	
	return lines;
}

Boolean UiShowScrollbar(PanelList* list){
	
	return UiNumLinesNeeded(list) > prvUiGetConst(CONST_NUM_ROWS);
	
}

BitmapPtr UiGetSmallIcon(PanelList* list,UInt32 cat,UInt32 item,Boolean* release){
	
	BitmapPtr bmp;
	UInt32 crid;
	
	PanelListGetNthPanelInCategory(list,cat,item,&crid,NULL,&bmp,NULL);
	
	if(bmp){
		
		*release = false;
		return bmp;
	}
	else{
		
		UInt32 id = 0xF000;
		*release = true;
		
		if(crid == 'dttm'){
			
			id += 1;
		}
		else if(crid == 'frmt'){
			
			id += 2;
		}
		else if(crid == 'pinP'){
			
			id += 3;
		}
		else if(crid == 'SdAl'){
			
			id += 4;
		}
		else if(crid == 'digi'){
			
			id += 5;
		}
		else if(crid == 'scrt'){
			
			id += 6;
		}
		else if(crid == 'bttn'){
			
			id += 7;
		}
		else if(crid == 'ownr'){
			
			id += 8;
		}
		else if(crid == 'shct'){
			
			id += 9;
		}
		else if(crid == 'rotP'){
			
			id += 10;
		}
		else if(crid == 'abtp'){
			
			id += 11;
		}
		else if(crid == 'modm'){
			
			id += 12;
		}
		else if(crid == 'phop'){
			
			id += 13;
		}
		else if(crid == 'colP'){
			
			id += 14;
		}
		else if(crid == 'keyl'){
			
			id += 15;
		}
		else if(crid == 'Wpnl'){
			
			id += 16;
		}
		else if(crid == 'grfp'){
			
			id += 17;
		}
		else if(crid == 'GAny'){
			
			id += 18;
		}
		else if(crid == 'Vpnl'){
			
			id += 19;
		}
		else if(crid == 'netw'){
			
			id += 20;
		}
		
		
		return MemHandleLock(DmGetResource('tAIB',id));
	}
}

void UiDraw(PanelList* list,ViewState* vs,Boolean fullRedraw,Boolean resetScrollbar){
	
	UInt32 top = TOP_MARGIN;
	UInt32 lineNum = 0;
	UInt32 i,j,k,numP;
	Err err;
	char name[32];
	RGBColorType ofc,obc,otc;
	RectangleType rect;
	WinHandle wh;
	BitmapPtr bmp;
	RGBColorType xxx;
	
	if(fullRedraw){
		
		FormPtr fp = FrmGetActiveForm();
		UInt16 sclIdx = FrmGetObjectIndex(fp,1000);
		
		RctSetRectangle(&rect,0,0,prvUiGetConst(CONST_FORM_WIDTH),prvUiGetConst(CONST_FORM_HEIGHT));
		WinSetBounds(FrmGetWindowHandle(fp),&rect);
		
		if(resetScrollbar){
			RctSetRectangle(&rect,prvUiGetConst(CONST_FORM_WIDTH) - 7,TOP_MARGIN,7,prvUiGetConst(CONST_FORM_HEIGHT) - TOP_MARGIN);
			FrmSetObjectBounds(fp,sclIdx,&rect);
			
			if(UiShowScrollbar(list)){
				
				SclSetScrollBar(FrmGetObjectPtr(fp,sclIdx),vs->topRow,0,
						UiNumLinesNeeded(list) - prvUiGetConst(CONST_NUM_ROWS),2);
				FrmShowObject(fp,sclIdx);
			}
			else{
				
				FrmHideObject(fp,sclIdx);
			}
		}
		FrmDrawForm(fp);
	}
	/* 
		to prevent flickering as we erase and draw each item, we double buffer the item's small
		area using this offscreen window. It it fails to allocate, we can still proceed,
		but the user will see flicker
	*/
	
	wh = WinCreateOffscreenWindow(prvUiGetConst(CONST_COL_WIDTH),LINE_HEIGHT,nativeFormat,&err);
	if(wh) bmp = WinGetBitmap(wh);
	
	UIColorGetTableEntryRGB(UIObjectSelectedFill,&xxx);
	WinSetForeColorRGB(&xxx,&ofc);
	
	for(i = 0; i < PanelListGetNumCategories(list); i++){
		
		PanelListGetNthCategory(list,i,name,&numP);
		
		if(lineNum >= vs->topRow){
			
			FntSetFont(boldFont);
			WinDrawChars(name,StrLen(name),3,top);
			
			WinDrawLine(1,top+LINE_HEIGHT-2,prvUiGetConst(CONST_FORM_WIDTH) - (UiShowScrollbar(list)?9:2),
																					top+LINE_HEIGHT-2);
			FntSetFont(stdFont);
			top += LINE_HEIGHT;
		}
		
		lineNum++;
		
		if(lineNum >= vs->topRow + prvUiGetConst(CONST_NUM_ROWS)) return;
		
		
		for(j = 0; j < numP; j +=  prvUiGetConst(CONST_NUM_COLS)){
			
			
			if(lineNum >= vs->topRow){
				
				for(k = 0; k < prvUiGetConst(CONST_NUM_COLS); k++){
					
					if(j + k < numP){
						
						UInt16 topX,topY,drawX,drawY;
						BitmapPtr b;
						Boolean release;
						
						PanelListGetNthPanelInCategory(list,i,j + k,NULL,NULL,NULL,name);
						
						if(wh){
							
							topX = 0;
							topY = 0;
							drawX = LEFT_MARGIN + ((LEFT_MARGIN+prvUiGetConst(CONST_COL_WIDTH)) * k);
							drawY = top;
							wh = WinSetDrawWindow(wh);
							WinEraseWindow();
						}
						else{
							
							topX = LEFT_MARGIN + ((LEFT_MARGIN+prvUiGetConst(CONST_COL_WIDTH)) * k);
							topY = top;
						}
						
						if(vs->curCat == i && vs->curItem == j + k){
							
							UIColorGetTableEntryRGB(UIObjectSelectedFill,&xxx);
							WinSetBackColorRGB(&xxx,&obc);
							
							UIColorGetTableEntryRGB(UIObjectSelectedForeground,&xxx);
							WinSetTextColorRGB(&xxx,&otc);
						}
						
						RctSetRectangle(&rect,topX,topY,prvUiGetConst(CONST_COL_WIDTH),LINE_HEIGHT - 1);
						WinEraseRectangle(&rect,2);
						b = UiGetSmallIcon(list,i,j + k,&release);
						WinDrawBitmap(b,topX + 1,topY + ((LINE_HEIGHT - 9) / 2));
						if(release){
							
							MemHandle mh = MemPtrRecoverHandle(b);
							MemHandleUnlock(mh);
							DmReleaseResource(mh);
						}
						WinDrawChars(name,StrLen(name),topX + 17,topY + ((LINE_HEIGHT - FntLineHeight())/2));
						if(vs->curCat == i && vs->curItem == j + k){
							
							WinSetBackColorRGB(&obc,NULL);
							WinSetTextColorRGB(&otc,NULL);
						}
						
						if(wh){
							
							wh = WinSetDrawWindow(wh);
							WinDrawBitmap(bmp,drawX,drawY);
							
						}
					}
				}
				
				top += LINE_HEIGHT;
			}
			
			lineNum++;
			
			if(lineNum >= vs->topRow + prvUiGetConst(CONST_NUM_ROWS)) goto done;
		}
	}
	
	
done:
	
	WinSetForeColorRGB(&ofc,NULL);
	if(wh) WinDeleteWindow(wh,false);
}

Boolean UiGetSel(PanelList* list,ViewState* state,UInt16 x,UInt16 y,UInt32* selCat,UInt32* selItem){
	
	UInt32 i,j,num;
	
	if(y < TOP_MARGIN) return false;
	y -= TOP_MARGIN;
	y /= LINE_HEIGHT;
	
	if(y >= prvUiGetConst(CONST_NUM_ROWS)) return false;
	
	y += state->topRow;
	
	//now we have th eline height, time to see if it mathes anything
	
	for(i = 0; i < PanelListGetNumCategories(list); i++){
		
		if(y == 0) return false;		//tapped on category title itself
		y--;
		
		PanelListGetNthCategory(list,i,NULL,&num);
		
		for(j = 0; j < num; j+=prvUiGetConst(CONST_NUM_COLS)){
			
			if(y == 0){
				
				if(x < LEFT_MARGIN) return false;
				x -= LEFT_MARGIN;
				x /= LEFT_MARGIN+prvUiGetConst(CONST_COL_WIDTH);
				
				if(x >= prvUiGetConst(CONST_NUM_COLS)) return false;
				
				x += j;	//x is now selItem in this category
				
				if(x >= num) return false;
				
				*selCat = i;
				*selItem = x;
				return true;
			}
			y--;
		}
	}
	
	return false;
}

//return if we want to redraw...
static inline Boolean UiMoveSelection(PanelList* list,ViewState* state,UInt32 dir){
	
	UInt32 num;
	UInt16 e[2];
	UInt32 nc = prvUiGetConst(CONST_NUM_COLS);
	UInt32 nr = prvUiGetConst(CONST_NUM_ROWS);
	
	if(dir == KEY_DIR_RIGHT){
		
		PanelListGetNthCategory(list,state->curCat,NULL,&num);
		
		if(state->curItem + 1 == num){
			
			if(state->curCat +1 == PanelListGetNumCategories(list)){
				
				return false;
			}
			else{
				
				state->curCat++;
				state->curItem = 0;
			}
		}
		else{
			state->curItem++;
		}
	}
	else if(dir == KEY_DIR_LEFT){
		
		if(state->curItem == 0){
			
			if(state->curCat == 0){
				
				return false;
			}
			else{
				
				state->curCat--;
				
				PanelListGetNthCategory(list,state->curCat,NULL,&num);
				state->curItem = num - 1;
			}
		}
		else{
			state->curItem--;
		}
	}
	else if(dir == KEY_DIR_UP){
		
		if(state->curItem >= nc){
			
			state->curItem -= nc;
		}
		else{
			
			if(state->curCat == 0){
				
				return false;
			}
			else{
				
				state->curCat--;
				PanelListGetNthCategory(list,state->curCat,NULL,&num);
				
				if((num % nc)  == 0){
					
					state->curItem = num - nc + (state->curItem % nc);
					
				}
				else if((num % nc) > (state->curItem % nc)){
					
					state->curItem = num - (num % nc) + (state->curItem % nc);
					
				}
				else if(num >= nc){
					
					state->curItem = num - (num % nc) - nc + (state->curItem % nc);
					
				}
				else{
					
					state->curItem = 0;
				}
			}
		}
	}
	else if(dir == KEY_DIR_DOWN){
		
		PanelListGetNthCategory(list,state->curCat,NULL,&num);
		
		if(state->curItem + nc <num){
			
			state->curItem += nc;
		}
		else{
			
			Boolean done;
			UInt32 oldCat = state->curCat;
			
			while(state->curCat < PanelListGetNumCategories(list) - 1){
				
				state->curCat++;
				PanelListGetNthCategory(list,state->curCat,NULL,&num);
				
				if(num > (state->curItem % nc)){
					
					state->curItem = (state->curItem % nc);
					done = true;
					break;
				}
			}
			
			if(state->curCat == oldCat) return false;
			if(!done) state->curItem = 0;
			
		}
	}
	
	num = UiItemLine(list,state->curCat,state->curItem);
	if(num < state->topRow){
		
		e[0] = SCREEN_AUTOSCROLLED_EVENT;
		e[1] = num;
		
		EvtAddEventToQueue((EventPtr)e);
		return false;
	}
	if(num - state->topRow >= nr){
		
		e[0] = SCREEN_AUTOSCROLLED_EVENT;
		e[1] = num - nr + 1;
		
		EvtAddEventToQueue((EventPtr)e);
		return false;
	}
	
	return true;
}

void UiLaunchPanel(PanelList* list,UInt32 cat,UInt32 num){
	
	UInt32 crid;
	char name[34];
	DmSearchStateType ss;
	LocalID LID;
	UInt16 cn;
	Err e;
	
	
	PanelListGetNthPanelInCategory(list,cat,num,&crid,NULL,NULL,name);
	
	e = DmGetNextDatabaseByTypeCreator(true,&ss,'panl',crid,true,&cn,&LID);
	
	if(e){
		
		StrCat(name,"\".");
		ErrAlertCustom(e,name,"Failed to launch preference panel \"",NULL);
	}
	else{
		
		SysUIAppSwitch(0,LID,sysAppLaunchCmdPanelCalledFromApp,NULL);
	}
}

Err displaySizeChangedCallback(SysNotifyParamType* p){
	
	UInt16 evt = SCREEN_RESIZED_EVENT;	//no need to waste stack space...
	
	EvtAddEventToQueue((EventPtr)&evt);
	
	return errNone;
}

void about(){
	
	FormPtr fp;
	
	fp = FrmInitForm(1001);
	prvSetFormExtendedFlags(fp,21,0x3500);
	FrmDoDialog(fp);
	FrmDeleteForm(fp);
}

/*
 * This is here to fix a f***ing annoying bug in 5-way nav. Scenario:
 * we catch the center key, and handle it. Seems liek OS shoudl leave
 * it alone, no? Well, NO. It rememebers it, and when a form comes up
 * it processes it again, thus clicking the default button. The
 * practical upshot of this is taht when we launch the panel we need
 * on a device with this bug, it will immediately close, as if user
 * clicked "Done". This function creates a fake window with a button
 * and gives the system up to half a second to close it. This drains
 * they system's cached state, and we can then safely launch the
 * needed panel. On devices without this bug, the form will self-
 * close in half a second. In resource file this form is 10x10, with
 * topLeft set to (65526,65526). This might seem strange, but in fact
 * its topLeft is (-10,-10) as palmos will interpret those values as
 * SIGNED short. Thus the form is not at all visible, and thus cannot
 * bother the user.
 */

void UiHackKillEvents(){
	
	UInt32 t = TimGetTicks();
	FormPtr fp,oaf;
	EventType e;
	Err err;
	
	oaf = FrmGetActiveForm();
	fp = FrmInitForm(9999);
	FrmSetActiveForm(fp);
	FrmDrawForm(fp);
	do{
		
		EvtGetEvent(&e,evtWaitForever);
		
		if(!SysHandleEvent(&e)){
			
			if(!MenuHandleEvent(NULL, &e, &err)){
				
				if(!FrmHandleEvent(fp, &e)){
					
					//nothing really...
				}
			}
		}
	}while(TimGetTicks() < t + 50 && e.eType != ctlSelectEvent);
	
	FrmEraseForm(fp);
	FrmSetActiveForm(oaf);
	FrmDeleteForm(fp);
}

void UI(PanelList* list){
	
	FormPtr fp;
	EventType e;
	Err err;
	ViewState state;
	UInt32 tmp;
	LocalID LID;
	UInt32 oldCat,oldItem;
	RectangleType form,clear;
	
	state.topRow = 0;
	state.curCat = 0;
	state.curItem = 0;
	
	if(SysCurAppDatabase((UInt16*)&err/*who needs cardNo anyways?*/,&LID)){
		
		ErrAlertCustom(0,"WTF?",NULL,NULL);
		
	}
	
	fp = FrmInitForm(1000);
	FrmSetActiveForm(fp);
	
	SysNotifyRegister(0,LID,'scrs',&displaySizeChangedCallback,sysNotifyNormalPriority,NULL);
	
	if(errNone == FtrGet(pinCreator, pinFtrAPIVersion, &tmp) && tmp){
		 
		//FrmSetDIAPolicyAttr(fp,frmDIAPolicyCustom);
	}
	
	prvSetFormExtendedFlags(fp,21,0x1502);
	
	UiDraw(list,&state,true,true);
	
	do{
		
		EvtGetEvent(&e,evtWaitForever);
		
		if(!SysHandleEvent(&e)){
			
			if(!MenuHandleEvent(NULL, &e, &err)){
				
				if(!FrmHandleEvent(fp, &e)){
					
					switch(e.eType){
						
						case SCREEN_RESIZED_EVENT:
							
							((UInt16*)&e)[1] = 1;		//nasty but cool :-)
							//fallthrough intentional
							
						case SCREEN_AUTOSCROLLED_EVENT:
							
							state.topRow = ((UInt16*)&e)[1];
							UiDraw(list,&state,true,true);
							break;
						
						case penDownEvent:
							
							oldCat = state.curCat;
							oldItem = state.curItem;
							
							if(UiGetSel(list,&state,e.screenX,e.screenY,&state.curCat,&state.curItem)){
								
								Coord x = e.screenX,y = e.screenY;
								Boolean down = true;
								UInt32 downCat = state.curCat,downItem = state.curItem;
								UInt32 inCat,inItem;
								
								do{
									
									if(UiGetSel(list,&state,x,y,&inCat,&inItem)){
										
										if(inCat == downCat && inItem == downItem){
											
											state.curItem = inItem;
											state.curCat = inCat;
										}
										else{
											
											state.curCat = -1;
										}
									}
									else{
										
										state.curCat = -1;
										inCat = -1;
									}
									
									UiDraw(list,&state,false,true);
									EvtGetPen(&x,&y,&down);
									
								}while(down);
								
								if(inCat == downCat && inItem == downItem){
									
									UiLaunchPanel(list,inCat,inItem);
									
								}
								else{
									
									state.curCat = oldCat;
									state.curItem = oldItem;
									
									UiDraw(list,&state,false,true);
								}
							}
							
							break;
						
						case keyDownEvent:
							
							tmp = keyInfo(&e,false);
							if(tmp == KEY_DIR_UNKNOWN) break;
							if(tmp == KEY_DIR_CENTER){
								
								UiHackKillEvents();
								UiLaunchPanel(list,state.curCat,state.curItem);
							}
							if(UiMoveSelection(list,&state,tmp)){
								
								UiDraw(list,&state,false,true);
							}
							break;
						
						case sclRepeatEvent:
							
							tmp = e.data.sclRepeat.newValue;
							goto scroll_screen;
						
						case sclExitEvent:
							
							tmp = e.data.sclExit.newValue;
scroll_screen:
							RctSetRectangle(&form,0,TOP_MARGIN,
									prvUiGetConst(CONST_FORM_WIDTH) - (UiShowScrollbar(list)?7:0),
														prvUiGetConst(CONST_FORM_HEIGHT) - TOP_MARGIN);
							WinScrollRectangle(&form,(tmp > state.topRow) ? winUp : winDown,
								((tmp > state.topRow)?(tmp - state.topRow):(state.topRow - tmp))*LINE_HEIGHT,&clear);
							WinEraseRectangle(&clear,0);
							state.topRow = tmp;
							UiDraw(list,&state,false,e.eType == sclExitEvent);
							break;
						
						case ctlSelectEvent:
							
							FrmCustomAlert(10024,"hi",NULL,NULL);
							break;
							
						case menuEvent:
							
							about();
							break;
						//other event cases here...
					}
				}
			}
		}
		
	}while(e.eType != appStopEvent);
	
	SysNotifyUnregister(0,LID,'scrs',sysNotifyNormalPriority);
	
	FrmEraseForm(fp);
	FrmSetActiveForm(NULL);
	FrmDeleteForm(fp);
}

void ListAllPanels(PanelList* list){
	
	MemHandle panelsH;
	UInt16 i,numPanels;
	SysDBListItemType* panels;
	
	if(SysCreatePanelList(&numPanels,&panelsH)){
		
		panels = MemHandleLock(panelsH);
		
		for(i = 0; i < numPanels; i++){
			
			DmOpenRef db;
			MemHandle mh,nameH = NULL,catH = NULL;
			BitmapPtr icon = NULL;
			BitmapPtr iconS = NULL;
			char* name;
			char* cat = NULL;
			
			db = DmOpenDatabase(0,panels[i].dbID,dmModeReadOnly);
			
			DmDatabaseProtect(0,panels[i].dbID,true);
			
			mh = DmGet1Resource('tAIB',1000);
			if(mh){
				
				icon = MemHandleLock(mh);
			}
			
			
			mh = DmGet1Resource('tAIB',1001);
			if(mh){
				
				iconS = MemHandleLock(mh);
			}
			
			
			mh = DmGet1Resource('tAIN',1000);
			if(mh){
				
				name = MemHandleLock(nameH = mh);
			}
			else{
				
				name = panels[i].name;
			}
			
			
			mh = DmGet1Resource('taic',1000);
			if(!mh) mh = DmGet1Resource('taic',1100);
			if(mh){
				
				cat = MemHandleLock(catH = mh);
			}
			
			PanelListAddPanel(list,panels[i].creator,icon,iconS,name,cat);
			
			if(nameH){
				
				MemHandleUnlock(nameH);
				DmReleaseResource(nameH);
			}
			
			if(catH){
				
				MemHandleUnlock(catH);
				DmReleaseResource(catH);
			}
			
			DmCloseDatabase(db);
		}
		
		MemHandleUnlock(panelsH);
		MemHandleFree(panelsH);
	}
}

void PanelDelProc(UInt32 crid,char* name,BitmapPtr icon,BitmapPtr iconS){
	
	DmSearchStateType ss;
	MemHandle mh;
	DmOpenRef db = NULL;
	LocalID LID;
	UInt16 cn;
	Err e;
	
	e = DmGetNextDatabaseByTypeCreator(true,&ss,'panl',crid,true,&cn,&LID);
	
	if(e == errNone){
		
		if(icon){
			
			mh = MemPtrRecoverHandle(icon);
			MemHandleUnlock(mh);
			DmReleaseResource(mh);
		}
		
		if(iconS){
			
			mh = MemPtrRecoverHandle(iconS);
			MemHandleUnlock(mh);
			DmReleaseResource(mh);
		}
		
		DmDatabaseProtect(0,LID,false);
	}
}


UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	
	if(cmd == 0 || cmd == sysAppLaunchCmdReturnFromPanel){
		
		PanelList* panels;
		
		panels = PanelListCreate(&PanelDelProc);
		
		ListAllPanels(panels);
		
		if(PanelListGetNumPanels(panels)){
			
			UI(panels);
			
		}
		else{
			
			FrmAlert(1000);
		}
		
		PanelListDestroy(panels);
	}

	return 0;
}
