/* 
 * UAE - The Un*x Amiga Emulator
 *
 * BeBox port specific stuff
 * 
 * (c) 1996 Christian Bauer
 * (c) 1996 Patrick Hanevold
 */

#define VersionStr "0.6.4"

#include <AppKit.h>
#include <InterfaceKit.h>
#include <KernelKit.h>
#include <Record.h>
#include <File.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define __STDC__ 1
#define __GNU_LIBRARY__
#include <getopt.h>
#undef __GNU_LIBRARY__
#undef __STDC__

#ifndef __bebox__
#error Compiling bebox.cpp, but __bebox__ unset.
#endif

#include "bebox.h"

extern void deinit_sound(void);


/*
 *  Create application object and start it
 */

UAE *the_app;

main()
{
	the_app = new UAE();
	the_app->Run();
	delete the_app;
	
	return 0;
}


/*
 *  UAE Constructor: Initialize member variables
 */

UAE::UAE() : BApplication('UAEm')
{
	the_bitmap = NULL;
	the_emulator = NULL;
	main_window = NULL;
	window_open = FALSE;
// Init Key events
	CurentEvent = (Event*)malloc(sizeof(Event));
	LastEvent=CurentEvent;
	CurentEvent->Age=0;
	CurentEvent->Next=0;
}

void UAE::ArgvReceived(int argc, char **argv)
{
	parse_cmdline(argc,argv);
}

/*
 *  Arguments processed, create and start emulation
 */

void UAE::ReadyToRun(void)
{
	vsize = correct_aspect ? 2*numscrlines : numscrlines;
	hsize = use_lores ? 400 : 800;
	hpixels = use_lores ? 320 : 796;

	// Allocate bitmap
	the_bitmap = new BBitmap(BRect(0, 0, hsize-1, vsize-1), B_COLOR_8_BIT);

	// Set up vidinfo
	gfxvidinfo.bufmem =  (char *)the_bitmap->Bits();
	gfxvidinfo.rowbytes = the_bitmap->BytesPerRow();
	gfxvidinfo.pixbytes = 1;
	gfxvidinfo.maxblocklines = 100;
	gfxvidinfo.maxlinetoscr = 0;
        gfxvidinfo.x_adjust = 0;
        gfxvidinfo.maxline = 100000; /* ??? */

	// Open window
	main_window = new UAEWindow(BRect(0, 0, hpixels-1, vsize+B_MENU_BAR_HEIGHT), the_bitmap);

	// Initialize mouse and keyboard variables
	buttonstate[0] = buttonstate[1] = buttonstate[2] = FALSE;
	lastmx = lastmy = 0;
	newmousecounters = FALSE;
	inwindow = TRUE;

	// Start emulation
	the_emulator = new Emulator;
	the_emulator->Run();
}


/*
 *  Quit requested (either by menu or by closing the window)
 */

bool UAE::QuitRequested(void)
{
	if (BApplication::QuitRequested()) {

		// Stop emulation
		if (the_emulator) {
			the_emulator->Quit();
			delete the_emulator;
		}

		// Deallocate bitmap
		if (the_bitmap)
			delete the_bitmap;

		return TRUE;
	}
	return FALSE;
}


/*
 *  Display "about" window
 */

void UAE::AboutRequested(void)
{
	char str[256];

	sprintf(str, "     Un*x Amiga Emulator V"VersionStr"\n"
		     "          by Bernd Schmidt\n"
		     "    BeBox port by Christian Bauer\n"
		     "Additional porting by Patrick Hanevold");
	BAlert *the_alert = new BAlert("", str, "OK");
	the_alert->Go();
}

//void UAE::MessageReceived(BMessage *Msg)
//{
//	switch(Msg->what)
//	{
//	default:
//		BApplication::MessageReceived(Msg);
//	}
//}

char *DiskName[4];
char *FileName[4];
int	FloppyN;

void UAE::RefsReceived(BMessage *Msg)
{
	ulong	type;
	long	count;
	BFile	*File;
	BDirectory *dir,*dir2;
	char FileNm[256]; // = (char*)Msg->FindString("filename");
	char Path1[512]="";
	char Path2[512]="";
	int	err;

	Msg->GetInfo("refs",&type,&count);
	for(long n=--count;n>=0;n--){
		record_ref item = Msg->FindRef("refs",n);
		if(item.database>=0 && item.record>=0){
			if(does_ref_conform(item,"File")){
				File = new BFile;
				File->SetRef(item);
				File->GetName(FileNm);
				DiskName[FloppyN]=FileNm;
				File->GetName(Path2);
				dir = new BDirectory;
				dir2 = new BDirectory;
				err = File->GetParent(dir);
				while(err == B_NO_ERROR)
				{
					dir->GetName(Path1);
					if(strlen(Path2))
					{
						strcat(Path1,"/");
						strcat(Path1,Path2);
					}
					strcpy(Path2,Path1);
					err = dir->GetParent(dir2);
					if(err==B_NO_ERROR) dir->SetRef(dir2->Record()->Ref());
				}
				strcpy(Path1,"/");
				strcat(Path1,Path2);
//				strcpy(FileName[FloppyN],Path1);
				FileName[FloppyN]=Path1;
				disk_eject(FloppyN);
				disk_insert(FloppyN,Path1);
				delete(dir);
				delete(dir2);
				delete(File);
			}
			//else BRecord *rec = new BRecord(item);
		}
	}
}

void UAE::FilePanelClosed(BMessage *Msg)
{
}

void UAE::RequestFile(char *Title,int Drive)
{
	FloppyN=Drive;
	RunFilePanel(Title,"Insert","Eject",FALSE,NULL);
}

/*
 *  UAE Window constructor
 */

LEDView *PowerLED,*DriveLED;

#define M_DF0 'MDF0'
#define M_DF1 'MDF1'
#define M_DF2 'MDF2'
#define M_DF3 'MDF3'
#define M_FR1 'MFR1'
#define M_FR2 'MFR2'
#define M_FR3 'MFR3'
#define M_FR4 'MFR4'
#define M_FR5 'MFR5'
#define M_FR6 'MFR6'
#define M_FR7 'MFR7'

UAEWindow::UAEWindow(BRect frame, BBitmap *bitmap) : BWindow(frame,"UAE "VersionStr, B_TITLED_WINDOW, B_NOT_RESIZABLE|B_NOT_ZOOMABLE)
{
	BRect MenuRect   = Bounds();
	BRect PowerRect  = Bounds();
	BRect DriveRect  = Bounds();
	BRect MeterRect	 = Bounds();
	MenuRect.bottom  = MenuRect.top + B_MENU_BAR_HEIGHT;
	MenuRect.right  -= 8;
	DriveRect.bottom = MenuRect.bottom;
	DriveRect.left	 = MenuRect.right+1;
	DriveRect.right	 = DriveRect.left+3;
	PowerRect.bottom = MenuRect.bottom;
	PowerRect.left	 = DriveRect.right+1;
	PowerRect.right	 = PowerRect.left+3;
	MeterRect.left	+= 50;
	MeterRect.right -= 15;
	MeterRect.top	+= 5;
	MeterRect.bottom = B_MENU_BAR_HEIGHT-5;

	BView *DriveView = new BView(DriveRect,"",B_FOLLOW_NONE, B_WILL_DRAW);

	BMenuBar *MenuBar = new BMenuBar(MenuRect,"MenuBar",B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP, B_ITEMS_IN_ROW, FALSE);

	BMenu 		*Menu;
	BMenu		*SubMenu;
	BMenuItem	*DriveItem[4],*RateItem[7];

	DiskName[0]=df0;
	DiskName[1]=df1;
	DiskName[2]=df2;
	DiskName[3]=df3;
	Menu = new BMenu("Amiga", B_ITEMS_IN_COLUMN); MenuBar->AddItem(Menu);
		SubMenu = new BMenu("Floppy", B_ITEMS_IN_COLUMN); Menu->AddItem(SubMenu);
			DriveItem[0] = new BMenuItem(DiskName[0], new BMessage(M_DF0), NULL); 	SubMenu->AddItem(DriveItem[0]);
			DriveItem[1] = new BMenuItem(DiskName[1], new BMessage(M_DF1), NULL); 	SubMenu->AddItem(DriveItem[1]);
			DriveItem[2] = new BMenuItem(DiskName[2], new BMessage(M_DF2), NULL); 	SubMenu->AddItem(DriveItem[2]);
			DriveItem[3] = new BMenuItem(DiskName[3], new BMessage(M_DF3), NULL); 	SubMenu->AddItem(DriveItem[3]);
		SubMenu = new BMenu("Framerate", B_ITEMS_IN_COLUMN); Menu->AddItem(SubMenu);
			SubMenu->SetRadioMode(TRUE);
			RateItem[0] = new BMenuItem("1/1", new BMessage(M_FR1), NULL);			SubMenu->AddItem(RateItem[0]);
			RateItem[1] = new BMenuItem("1/2", new BMessage(M_FR2), NULL);			SubMenu->AddItem(RateItem[1]);
			RateItem[2] = new BMenuItem("1/3", new BMessage(M_FR3), NULL);			SubMenu->AddItem(RateItem[2]);
			RateItem[3] = new BMenuItem("1/4", new BMessage(M_FR4), NULL);			SubMenu->AddItem(RateItem[3]);
			RateItem[4] = new BMenuItem("1/5", new BMessage(M_FR5), NULL);			SubMenu->AddItem(RateItem[4]);
			RateItem[5] = new BMenuItem("1/6", new BMessage(M_FR6), NULL);			SubMenu->AddItem(RateItem[5]);
			RateItem[6] = new BMenuItem("1/7", new BMessage(M_FR7), NULL);			SubMenu->AddItem(RateItem[6]);
	RateItem[3]->SetMarked(TRUE);

	AddChild(MenuBar);
//	AddChild(DriveView);
//	Lock();
//	DriveView->SetHighColor(0xff,0,0);
//	DriveView->FillRect(DriveRect,B_SOLID_HIGH);
//	Unlock();

//	SetPulseRate(500000.0/100.0);

	DriveLED = new LEDView(DriveRect,0xff,0x80,0,0x80,0x40,0);
	AddChild(DriveLED);
	PowerLED = new LEDView(PowerRect,0,0xe0,0,0,0xb0,0);
	AddChild(PowerLED);
	MeterView *Meter = new MeterView(MeterRect);
	AddChild(Meter);

	int r, g, b, i;

	// Move window to right position
	MoveTo(80, 60);

	// Create bitmap view
	Lock();
	frame.Set(0,B_MENU_BAR_HEIGHT+1,hsize-1,vsize+B_MENU_BAR_HEIGHT);
	main_view = new BitmapView(frame, bitmap);
	AddChild(main_view);
	main_view->MakeFocus();
	main_view->GetKeys(&old_key_info, FALSE);
	Unlock();

	bitmap_view = main_view;
	bitmap_window = this;

	// Initialize xcolors
	i = 0;
	for (r=0; r<16; r++) {
		for (g=0; g<16; g++) {
			for (b=0; b<16; b++)
				xcolors[i++] = index_for_color(r<<4 | r, g<<4 | g, b<<4 | b);
		}
	}

	// Show window
	Show();
	window_open = TRUE;
}

void UAEWindow::MessageReceived(BMessage *Msg)
{
	switch(Msg->what)
	{
	case M_DF0:
		the_app->RequestFile("Insert floppy in DF0:",0);
	break;
	case M_DF1:
		the_app->RequestFile("Insert floppy in DF1:",1);
	break;
	case M_DF2:
		the_app->RequestFile("Insert floppy in DF2:",2);
	break;
	case M_DF3:
		the_app->RequestFile("Insert floppy in DF3:",3);
	break;
	case M_FR1:
		framerate = 1;
	break;
	case M_FR2:
		framerate = 2;
	break;
	case M_FR3:
		framerate = 3;
	break;
	case M_FR4:
		framerate = 4;
	break;
	case M_FR5:
		framerate = 5;
	break;
	case M_FR6:
		framerate = 6;
	break;
	case M_FR7:
		framerate = 7;
	break;
	default:
		printf("Err\n");
		BWindow::MessageReceived(Msg);
	}
}

/*
 *  Closing the window quits UAE
 */

bool UAEWindow::QuitRequested(void)
{
	window_open = FALSE;
	be_app->PostMessage(B_QUIT_REQUESTED);
	return TRUE;
}


/*
 *  Window was (de)activated
 */

void UAEWindow::WindowActivated(bool active)
{
	inwindow = active;
}

/*
 *  Bitmap view constructor
 */

BitmapView::BitmapView(BRect frame, BBitmap *bitmap) : BView(frame, "", B_FOLLOW_NONE, B_WILL_DRAW)
{
	the_bitmap = bitmap;
}


/*
 *  Blit the bitmap
 */

void BitmapView::Draw(BRect update)
{
	int xs = use_lores ? prev_max_diwstop - 328 : 0;
	BRect from=update;
	from.left+=xs;
	from.right+=xs;
	DrawBitmap(the_bitmap, from, update);
}

void BitmapView::Draw(BRect from, BRect to)
{
	DrawBitmap(the_bitmap, from, to);
}

//char KeyState[256];
char RawKeys[128]={
//      	0      1    2    3    4    5    6    7    8     9      A      B            C            D        E    F
/* 00 */	0,     0,   0,   0,   0,   0,   0,   0,   AK_BS,AK_TAB,AK_RET,0,           0,           0,       0,   0,
/* 10 */	0,     0,   0,   0,   0,   0,   0,   0,   0,    0,     0,     AK_ESC,      0,           0,       0,   0,
/* 20 */	AK_SPC,0,   0,   0,   0,   0,   0,   0,   0,    0,     0,     AK_NPADD,    0,           AK_NPSUB,0,   0,
/* 30 */	AK_0,  AK_1,AK_2,AK_3,AK_4,AK_5,AK_6,AK_7,AK_8, AK_9,  0,     AK_SEMICOLON,0,           0,       0,   0,
/* 40 */	0,     AK_A,AK_B,AK_C,AK_D,AK_E,AK_F,AK_G,AK_H, AK_I,  AK_J,  AK_K,        AK_L,        AK_M,    AK_N,AK_O,
/* 50 */	AK_P,  AK_Q,AK_R,AK_S,AK_T,AK_U,AK_V,AK_W,AK_X, AK_Y,  AK_Z,  0,           AK_BACKSLASH,0,       0,   0,
/* 60 */	0,     AK_A,AK_B,AK_C,AK_D,AK_E,AK_F,AK_G,AK_H, AK_I,  AK_J,  AK_K,        AK_L,        AK_M,    AK_N,AK_O,
/* 70 */	AK_P,  AK_Q,AK_R,AK_S,AK_T,AK_U,AK_V,AK_W,AK_X, AK_Y,  AK_Z,  0,           0,           0,       0,   AK_DEL};

void BitmapView::KeyDown(ulong aChar)
{
//	printf("$%x\n",aChar);
/*
	Event *NewEvent=(Event*)malloc(sizeof(Event));
	NewEvent->Next=0;
	NewEvent->Age=3;
	if((aChar>0x40)&&(aChar<0x5b)){
		NewEvent->Key=AK_LSH;
		LastEvent->Next=NewEvent;
		LastEvent=NewEvent;
		NewEvent=(Event*)malloc(sizeof(Event));
		NewEvent->Next=0;
		NewEvent->Age=3;
		NewEvent->Key=RawKeys[aChar];
		LastEvent->Next=NewEvent;
		LastEvent=NewEvent;
		NewEvent=(Event*)malloc(sizeof(Event));
		NewEvent->Next=0;
		NewEvent->Age=3;
		NewEvent->Key=AK_LSH;
	}else{
		switch(aChar)
		{
		case 0x3a:
			NewEvent->Key=AK_LSH;
			LastEvent->Next=NewEvent;
			LastEvent=NewEvent;
			NewEvent=(Event*)malloc(sizeof(Event));
			NewEvent->Next=0;
			NewEvent->Age=3;
			NewEvent->Key=AK_SEMICOLON;
			LastEvent->Next=NewEvent;
			LastEvent=NewEvent;
			NewEvent=(Event*)malloc(sizeof(Event));
			NewEvent->Next=0;
			NewEvent->Age=3;
			NewEvent->Key=AK_LSH;
		break;
		default:
			NewEvent->Key=RawKeys[aChar];
		}
	}
	LastEvent->Next=NewEvent;
	LastEvent=NewEvent;
*/
}

/*
 *		LED
 */

LEDView::LEDView(BRect frame, int aR, int aG, int aB, int iR, int iG, int iB) : BView(frame,"", B_FOLLOW_NONE, B_WILL_DRAW)
{
	ActiveColor.R=aR;
	ActiveColor.G=aG;
	ActiveColor.B=aB;
	IdleColor.R=iR;
	IdleColor.G=iG;
	IdleColor.B=iB;
	State = FALSE;
}

void LEDView::AttachedToWindow(void)
{
	bounds = Bounds();
}

void LEDView::Draw(BRect update)
{
	Refresh();
}

void LEDView::SetState(bool NewState)
{
	if(NewState!=State)
	{
		State = NewState;
		Refresh();
	}
}

void LEDView::Refresh(void)
{
	Window()->Lock();
	if(State)	SetHighColor(ActiveColor.R,ActiveColor.G,ActiveColor.B);
	else		SetHighColor(IdleColor.R,IdleColor.G,IdleColor.B);
	FillRect(bounds);
	Window()->Unlock();
}

/*
 *	Meter
 */

MeterView::MeterView(BRect frame) : BView(frame,"", B_FOLLOW_NONE, B_WILL_DRAW|B_PULSE_NEEDED)
{
}

void MeterView::AttachedToWindow(void)
{
	bounds = Bounds();
}

void MeterView::Draw(BRect)
{
	Refresh();
}

void MeterView::Pulse(void)
{
	Refresh();
}

void MeterView::Refresh(void)
{
	ulong	Speed=frametime/timeframes;
	int		Frames=Speed/20+1;
	BRect	Fine=bounds;
	Fine.left=Fine.right-(bounds.right-bounds.left)/Frames;
	Fine.right=Fine.left+(Speed-20*(Frames-1))*((bounds.right-bounds.left)/Frames)/20;
	Window()->Lock();
	SetHighColor(0,0,0);
	FillRect(bounds);
	SetHighColor(255,0,0);
	FillRect(Fine);
	int R=255-200/Frames;
	for(int n=Frames-1; n; n--){
		R-=200/Frames;
		SetHighColor(R,0,0);
		Fine.left=(n-1)*((bounds.right-bounds.left)/Frames);
		Fine.right=n*((bounds.right-bounds.left)/Frames);
		FillRect(Fine);
	}
	Window()->Unlock();
}

/*
 *  Start main emulation thread
 */

void Emulator::Run(void)
{
	// Initialize everything	
	produce_sound = TRUE;
	init_sound();
	gui_init();
	init_joystick();
	keybuf_init ();    
	memory_init();
	custom_init();
	DISK_init();
	init_m68k();
	MC68000_reset();

	// Start the emulation thread
	the_thread = spawn_thread(thread_invoc, "UAE 68000", B_NORMAL_PRIORITY, this);
	resume_thread(the_thread);
	thread_running = TRUE;
}


/*
 *  Stop main emulation thread
 */

void Emulator::Quit(void)
{
	// Kill the thread if it is running
	if (thread_running) {
		kill_thread(the_thread);
		thread_running = FALSE;
	}

	close_joystick();
	deinit_sound();
}


/*
 *  The thread's main function
 */

long Emulator::thread_invoc(void *data) //Emulator *obj)
{
	Emulator *obj = (Emulator*)data;
	obj->thread_func();
	return 0;
}

void Emulator::thread_func(void)
{
	// This jumps to MC68000_run() and executes the main loop
	debug();
	thread_running = FALSE;
}


/*
 *  Redraw a line
 */

void flush_line(int y)
{
	if (window_open) {
		bitmap_window->Lock();
		bitmap_view->Draw(BRect(0, y, hpixels+-1, y));
		bitmap_window->Unlock();
	}
}


/*
 *  Redraw a block
 */

void flush_block(int ystart, int ystop)
{
	int xs = use_lores ? prev_max_diwstop - 328 : 0;

	if (window_open) {
		bitmap_window->Lock();
		bitmap_view->Draw(BRect(xs, ystart, hpixels+xs-1, ystop), BRect(0, ystart, hpixels-1, ystop));
		bitmap_window->Unlock();
	}
}


/*
 *  Redraw the screen
 */

void flush_screen(int ystart, int ystop)
{
}


/*
 *  Poll mouse and keyboard
 */

void handle_events(void)
{
	key_info the_key_info;
	int be_code, be_byte, be_bit, amiga_code;
	BPoint mouse_point;
	ulong mouse_buttons;

	// Keyboard
	if (window_open && inwindow) {
		bitmap_window->Lock();
		bitmap_view->GetKeys(&the_key_info, FALSE);
		bitmap_window->Unlock();

		for (be_code=0; be_code<0x80; be_code++) {
			be_byte = be_code >> 3;
			be_bit = 1 << (~be_code & 7);

			// Key state changed?
			if ((the_key_info.key_states[be_byte] & be_bit)
			     != (old_key_info.key_states[be_byte] & be_bit)) {

				amiga_code = keycode2amiga[be_code];
				if (the_key_info.key_states[be_byte] & be_bit) {

					// Key pressed
					if (amiga_code == AK_mousestuff)
						togglemouse();
					else
						printf("Key\n");
						record_key(amiga_code << 1);
				} else {

					// Key released
					record_key((amiga_code << 1) | 1);
				}
			}
		}		
		old_key_info = the_key_info;

		// "Affengriff"
		if ((the_key_info.key_states[0x5c >> 3] & (1 << (~0x5c & 7)))
		 && (the_key_info.key_states[0x5d >> 3] & (1 << (~0x5d & 7)))
		 && (the_key_info.key_states[0x5f >> 3] & (1 << (~0x5f & 7))))
			MC68000_reset();

		// Scroll lock toggles inhibit_frame
		inhibit_frame = the_key_info.key_states[0x0f >> 3] & (1 << (~0x0f & 7));
/*
		static bool ShiftState=FALSE;
		if(CurentEvent->Age){
			if(CurentEvent->Key==AK_LSH){
				CurentEvent->Age--;
				if(CurentEvent->Age==2){
					if(ShiftState) {record_key((AK_LSH<<1)|1); ShiftState=FALSE; printf("ShU\n");}
					else           {record_key(AK_LSH<<1); ShiftState=TRUE; printf("ShD\n");}
				}
			}else{
				CurentEvent->Age--;
				if(CurentEvent->Age==2) record_key(CurentEvent->Key<<1);
				if(CurentEvent->Age==0) record_key((CurentEvent->Key<<1)|1);
			}
		}
		else{
			if(CurentEvent->Next)
			{
				Event *TrashEvent=CurentEvent;
				CurentEvent=CurentEvent->Next;
				free(TrashEvent);
			}
		}
*/
	}
	// Mouse
	if (window_open && inwindow) {
		bitmap_window->Lock();
		bitmap_view->GetMouse(&mouse_point, &mouse_buttons, FALSE);
		bitmap_window->Unlock();
		lastmx = mouse_point.x;
		lastmy = mouse_point.y;

		if(lastmx>=0&&lastmy>=0&&lastmx<hsize&&lastmy<vsize){
			buttonstate[0] = mouse_buttons & B_PRIMARY_MOUSE_BUTTON;
			buttonstate[1] = mouse_buttons & B_TERTIARY_MOUSE_BUTTON;
			buttonstate[2] = mouse_buttons & B_SECONDARY_MOUSE_BUTTON;
		}
	}
}

int debuggable(void)
{
    return TRUE;
}

int needmousehack(void)
{
    return TRUE;
}

void LED(int on)
{
//	printf("PowerLED: %d\n",on);
	PowerLED->SetState((bool)~on);
}

bool LEDs[4];

void gui_led(int led, int on)
{
	if(led<4)
	{
		LEDs[led]=on;
		DriveLED->SetState(LEDs[0]|LEDs[1]|LEDs[2]|LEDs[3]);
//		printf("DriveLeds: %d,%d,%d,%d\n",LEDs[0],LEDs[1],LEDs[2],LEDs[3]);
	}
}

void calc_adjustment(void)
{
    gfxvidinfo.x_adjust = 0;
}

void target_specific_usage(void)
{
}

int quit_program;

static void sigchldhandler(int foo)
{
}

int gui_init(void)
{
	LEDs[0]=LEDs[1]=LEDs[2]=LEDs[3]=FALSE;
    quit_program = 0;
    return 0;
}

void gui_exit(void)
{
}

//void gui_led(int led, int on)
//{
//}

void gui_filename(int num, char *name)
{
}

static void getline(char *p)
{
}

void gui_handle_events(void)
{
}
