#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <linux/joystick.h>

#include <pthread.h>
#include <semaphore.h>


#include <vdr/osd.h>
#include <vdr/thread.h>
#include <vdr/plugin.h>
//#include "device.h"
//#include "status.h"

#include "gbcmenu.h"
#include "SDL/SDL.h"

#include "gbcosd.h"


#include <vdr/config.h>

#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "SDL/SDL.h"
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#else
#include "getopt.h"
#endif




#ifdef	__cplusplus
extern	"C"
{
#endif

  extern sem_t waitSetVideoMode;
#ifdef	__cplusplus
}
#endif


sem_t SemDrawThreadEnd;
sem_t SemActionThreadEnd;
sem_t SemJoyThreadEnd;

int EndAsked;

int GbStart;

int Transparency;


void AddEvent(int type,int key)
{
  EventList[posWritEventList].type = type;
  EventList[posWritEventList].key.keysym.sym = key;
  posWritEventList++;
  if(posWritEventList>=NBMAXEVT)
    posWritEventList = 0;

}


cgbcOsd::cgbcOsd()
{
  m_Number = 0;
  m_OldNumber = 0;
  m_InputTime = 0;

  m_lastRemoteKeyRead = 0;
  m_lastRemoteKeyWrite = 0;


  Transparency = 0xc0;

  GbStart	= 0;

  m_gbStarted = 0;

  m_iKeyboardMode = eMenuMode;
  m_ipause = 0;

  EndAsked = 0;

  m_Font = cFont::GetFont(fontSml);

  sem_init(&waitSetVideoMode,0,0);

  sem_init(&SemDrawThreadEnd,0,0);
  sem_init(&SemActionThreadEnd,0,0);
  sem_init(&SemJoyThreadEnd,0,0);

  VDRGBCLOG("cgbcOsd::cgbcOsd\n");
}

// m_Font->Height()
// m_Osd->DrawText(x, y, txt , clrWhite, clrBackground, m_Font);

cgbcOsd::~cgbcOsd(void)
{
  if( GbStart != 0 )
  {
    VDRGBCLOG("cgbcOsd::~cgbcOsd wait end Draw_thread ... ");
    sem_wait(&SemDrawThreadEnd);
    VDRGBCLOG("Ok\n");
  }

  if (m_pOsd)
    delete m_pOsd;
}

void cgbcOsd::Show(void)
{
  eOsdError Result;

  VDRGBCLOG("cgbcOsd::Show\n");

  m_pOsd = cOsdProvider::NewOsd(50,260);
  //      tArea Areas[] = { { 0, 0, 80, 80, 2 } };  //    160,h:144
  //     tArea Areas[] = { { 0, 0, 319, 320, 4 } };
  //     tArea Areas[] = { { 0, 0, 160, 160, 8 } };

  //      tArea Areas[] = { { 0, 0, 239, 240, 8 } };      // ok   ( 458880 )
  //      tArea Areas[] = { { 0, 0, 319, 320, 8 } };      // ko   ( 816640 )

  //      tArea Areas[] = { { 0, 0, 279, 280, 8 } };      // ok   ( 624960 )

  //tArea Areas[] = { { 0, 0, 159, 160, 8 } };    // ok   ( 624960 )
  tArea Areas[] = { { 0, 0, SIZEOSDX, SIZEOSDY, 8 } };    // ok   ( 624960 )

  Result = m_pOsd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea));
  if( Result == oeOk )
  {
    Result = m_pOsd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
    if( Result != oeOk )
    {
      printf("cgbcOsd SetAreas Error 0x�%x\n",Result);
    }
  }
  else
  {
    printf("cgbcOsd CanHandleAreas Error 0x%x\n",Result);
  }

  if( Result == oeOk )
  {
    VDRGBCLOG("cgbcOsd::Show Ok\n");
  }
  else
  {
    VDRGBCLOG("cgbcOsd::Show Err\n");
  }

  Start();
}


#ifdef __cplusplus
extern "C"
{
#endif


  extern Uint32 NextScreen[500][500];
  extern Uint32 NextScreenCpy;

  int mainVDR(char *argv);

#ifdef __cplusplus
}
#endif




void *Joy_Thread(void*ptr)
{
  int fd;
  //	int * axes;
  //	char numAxes;
  struct js_event e;
  int lastax,lastay;

  char *nm="/dev/js0";

  fd = open(nm, O_RDONLY);
  if (fd >= 0)
  {
    //		ioctl(fd, JSIOCGAXES, &numAxes);

    //		printf("joystick: numAxes = %d\n", numAxes);

    while( EndAsked == 0 )
    {
      if( read(fd,&e,sizeof(struct js_event)) > 0 )
      {

        //			printf("tm:%04x va:%04x ty:%04x nu:%04x \t ",e.time&0xffff,e.value&0xffff,e.type&0xffff,e.number&0xffff);

        if (e.type & JS_EVENT_INIT)
        {
          // Ignore
          printf( "ev INIT");
        }
        else if (e.type & JS_EVENT_BUTTON)
        {
          if (e.value == 1)
          {
            switch( e.number )
            {
            case 0 : AddEvent( SDL_KEYDOWN , SDLK_s );	break;
            case 1 : AddEvent( SDL_KEYDOWN , SDLK_d );	break;
            case 8 : AddEvent( SDL_KEYDOWN , SDLK_RSHIFT );	break;
            case 9 : AddEvent( SDL_KEYDOWN , SDLK_RETURN );	break;
            }


            //				printf( "Button %02d Down", e.number);
          }
          else
          {
            switch( e.number )
            {
            case 0 : AddEvent( SDL_KEYUP , SDLK_s );	break;
            case 1 : AddEvent( SDL_KEYUP , SDLK_d );	break;
            case 8 : AddEvent( SDL_KEYUP , SDLK_RSHIFT );	break;
            case 9 : AddEvent( SDL_KEYUP , SDLK_RETURN );	break;
            }
            //				printf( "Button %02d Up", e.number);
          }
        }
        else if (e.type & JS_EVENT_AXIS)
        {
          printf( "Axis %d Pos:%d", e.number, e.value);

          if( e.number == 0 )
          {
            if( e.value == -32767 )
            {
              AddEvent( SDL_KEYDOWN , SDLK_LEFT );
              lastax = SDLK_LEFT;
            }

            if( e.value == +32767 )
            {
              AddEvent( SDL_KEYDOWN , SDLK_RIGHT );
              lastax = SDLK_RIGHT;
            }

            if( e.value == 0 )
              AddEvent( SDL_KEYUP , lastax );

          }


          if( e.number == 1 )
          {
            if( e.value == -32767 )
            {
              AddEvent( SDL_KEYDOWN , SDLK_UP );
              lastay = SDLK_UP;
            }

            if( e.value == +32767 )
            {
              AddEvent( SDL_KEYDOWN , SDLK_DOWN );
              lastay = SDLK_DOWN;
            }

            if( e.value == 0 )
              AddEvent( SDL_KEYUP , lastay );

          }

        }

        //		printf("\n");
      }
    }
  }
  else
  {
    VDRGBCLOG("joystick: could not open device \n");
  }

  sem_post(&SemJoyThreadEnd);

  //	pthread_exit(0);

  return(0);
}


// SDLK_x,SDLK_w,SDLK_RSHIFT,SDLK_RETURN
// SDLK_UP


void *Action_Thread(void *ptr)
{
  char *romFileName;
  //    char *romFileName = "/data/games/GameBoy/WARIO.GB";
  //    char *romFileName = "../vdr-gngb/Tetris2.gb";
  //    char *romFileName = "../vdr-gngb/Marioland.gb";

  romFileName = (char *) ptr;

  mainVDR(romFileName);

  sem_post(&SemActionThreadEnd);

  //	pthread_exit(0);

  return(0);
}

void *Draw_Thread(void*ptr)
{
  int x,y;
  long c;
  float pasX , pasY;
  float posX , posY;
  int iposX,iposY;
  int sizeOsdY;
  const cFont *m_Font;

  m_Font = cFont::GetFont(fontSml);


  cOsd *pOsd = (cOsd *)ptr;

  sem_wait(&waitSetVideoMode);



  pasX = ScreenWidth/(float)SIZEOSDX;
  //    pasY = ScreenHeight/(float)SIZEOSDY;
  pasY = pasX;

  sizeOsdY = (int)((float)ScreenHeight / pasY);

  while( EndAsked == 0 )
  {
    posY = 0;
    NextScreenCpy = 1;
    for(y=0;y<sizeOsdY;y++)
    {
      iposY = (int)posY;

      posX = 0;
      for(x=0;x<SIZEOSDX;x++)
      {
        iposX = (int)posX;

        c = Transparency << 24;
        c |= NextScreen[iposX][iposY];
        pOsd->DrawPixel(x,y,c);

        posX += pasX;
      }
      posY += pasY;
    }
    NextScreenCpy = 0;

    pOsd->Flush();
    usleep(80000);
  }

  pOsd->DrawRectangle(0,0,SIZEOSDX,SIZEOSDY,clrGray50);
  pOsd->DrawText( 50 , 10 , "Bye Bye !", clrWhite, clrGray50, m_Font);
  pOsd->DrawText( 10 , 100 , "Press a GamePad Key...", clrWhite, clrGray50, m_Font);
  pOsd->Flush();

  VDRGBCLOG("cgbcOsd::~cgbcOsd wait end Joy_thread ... ");
  sem_wait(&SemJoyThreadEnd);
  VDRGBCLOG("Ok\n");

  pOsd->DrawRectangle(0,0,SIZEOSDX,SIZEOSDY,clrGray50);
  pOsd->DrawText( 50 , 10 , "Bye Bye !", clrWhite, clrGray50, m_Font);
  pOsd->DrawText( 10 , 100 , "Wait end of Emulator...", clrWhite, clrGray50, m_Font);
  pOsd->Flush();

  VDRGBCLOG("cgbcOsd::~cgbcOsd wait end Action_thread ... ");
  sem_wait(&SemActionThreadEnd);
  VDRGBCLOG("Ok\n");

  pOsd->DrawRectangle(0,0,SIZEOSDX,SIZEOSDY,clrTransparent);
  pOsd->Flush();

  sem_post(&SemDrawThreadEnd);

  //	pthread_exit(0);
  return(0);
}

void cgbcOsd::DrawBottomInfo( void )
{
  long c;
  int bottomheight;
  char txt[10];

  switch( m_iKeyboardMode )
  {
  case eTvMode:
    strcpy( txt ,"Tv");
    break;
  case eGbMode:
    strcpy( txt ,"Gb");
    break;
  case eGbPalMode:
    strcpy( txt ,"GbPal");
    break;
  default:
    strcpy( txt ,"--");
    break;

  }

  bottomheight = m_Font->Height();


  c = Transparency << 24;


  m_pOsd->DrawRectangle(0,SIZEOSDY-bottomheight,SIZEOSDX,SIZEOSDY,c | clrBlack);
  m_pOsd->DrawText(SIZEOSDX-30,SIZEOSDY-m_Font->Height(),txt, clrWhite, c | clrBlack, m_Font);
}


/*virtual*/
void cgbcOsd::Action(void)
{
  pthread_t thr_id;

  VDRGBCLOG("cgbcOsd::Action\n");


  char *romsName = ChooseRomMenu(this);
  m_iKeyboardMode = eTvMode;

  m_pOsd->DrawRectangle(0,0,SIZEOSDX,SIZEOSDY,clrGray50);
  DrawBottomInfo();

  VDRGBCLOG("cgbcOsd::StartRom : '");
  VDRGBCLOG(romsName);
  VDRGBCLOG("'\n");

  if( romsName != NULL )
  {
    GbStart	= 1;
    pthread_create(&thr_id, NULL, &Action_Thread, romsName);
    pthread_create(&thr_id, NULL, &Draw_Thread, m_pOsd);
    pthread_create(&thr_id, NULL, &Joy_Thread, m_pOsd);
  }
}

eOSState cgbcOsd::ProcessKey(eKeys keys)
{
  eOSState state; // = cOsdObject::ProcessKey(keys);

  //	if (state == osUnknown)
  {
    switch( m_iKeyboardMode )
    {
    case eMenuMode:
      state = ProcessKeyMenuMode(keys);
      break;

    case eGbMode:
      state = ProcessKeyGbMode(keys);
      break;

      /*			case eGbPalMode:
      				state = ProcessKeyGbPalMode(keys);
      				break;	*/

    case eTvMode:
    default:
      state = ProcessKeyTvMode(keys);
      break;
    }

    if( m_iKeyboardMode != eMenuMode )
      DrawBottomInfo();
  }

  if( state == osEnd )
  {
    EndAsked = 1;
    AddEvent( SDL_KEYDOWN , SDLK_ESCAPE );
    AddEvent( SDL_KEYUP , SDLK_ESCAPE );
  }

  return state;
}

eOSState cgbcOsd::ProcessKeyTvMode(eKeys keys)
{
  eOSState state;

  state = osContinue;
  switch (keys)
  {
    /*           case k0:
                if ((m_Number == 0) && (m_OldNumber != 0))
                {
                    m_Number = m_OldNumber;
                    m_OldNumber = cDevice::CurrentChannel();
                    Channels.SwitchTo(m_Number);
                    m_Number = 0;
                }
            	state = osContinue;
                break;
            case k1:
            case k2:
            case k3:
            case k4:
            case k5:
            case k6:
            case k7:
            case k8:
            case k9:
                if (m_Number >= 0)
                {
                    m_Number = m_Number * 10 + keys - k0;
                    if (m_Number > 0)
                    {
                        // DrawStatusWindow();
                        cChannel *ch = Channels.GetByNumber(m_Number);
                        m_InputTime = time_ms();
                        // Lets see if there can be any useful further input:
                        int n = ch ? m_Number * 10 : 0;
                        while (ch && (ch = Channels.Next(ch)) != NULL)
                        {
                            if (!ch->GroupSep())
                            {
                                if (n <= ch->Number() && ch->Number() <= n + 9)
                                {
                                    n = 0;
                                    break;
                                }
                                if (ch->Number() > n)
                                    n *= 10;
                            }
                        }
                        if (n > 0)
                        {
                            // This channel is the only one that fits the input, so let's take it right away:
                            m_OldNumber = cDevice::CurrentChannel();
                            Channels.SwitchTo(m_Number);
                            m_Number = 0;
                        }
                    }
                }
            break;
     
            case kNone:
                if (m_Number && (time_ms() - m_InputTime > CHANNELINPUT_TIMEOUT))
                {
                    if (Channels.GetByNumber(m_Number))
                    {
                        m_OldNumber = cDevice::CurrentChannel();
                        Channels.SwitchTo(m_Number);
                        m_Number = 0;
                    }
                    else
                    {
                        m_InputTime = time_ms();
                        m_Number = 0;
                    }
                }
            break;
                  */
  case kOk:
    m_iKeyboardMode = eGbMode;
    state = osContinue;
    break;
  case kBack:
    state = osEnd;
    break;

  case kUp:
    if( Transparency < 0xf0 )
      Transparency += 0x10;
    state = osContinue;
    break;

  case kDown:
    if( Transparency > 0x80 )
      Transparency -= 0x10;
    state = osContinue;
    break;

  default:
    break;

  }

  return state;
}

eOSState cgbcOsd::ProcessKeyGbMode(eKeys keys)
{
  eOSState state;

  state = osContinue;
  switch (keys)
  {
  case kOk:
    m_iKeyboardMode = eTvMode;
    break;

  case kBack:
    state = osEnd;
    break;

  case k7:		// Save Ram
    AddEvent( SDL_KEYDOWN , SDLK_F2 );
    AddEvent( SDL_KEYUP , SDLK_F2 );
    break;

  case k5:		// Pause
    if( m_ipause == 0 )
    {
      AddEvent( SDL_KEYDOWN , SDLK_F7 );
      AddEvent( SDL_KEYUP , SDLK_F7 );
      m_ipause = 1;
    }
    else
    {
      AddEvent( SDL_KEYDOWN , SDLK_F6 );
      AddEvent( SDL_KEYUP , SDLK_F6 );

      m_ipause = 0;
    }
    break;

  case k2:	//show fps
    AddEvent( SDL_KEYDOWN , SDLK_F10 );
    AddEvent( SDL_KEYUP , SDLK_F10 );
    break;


  case k6:
    AddEvent( SDL_KEYDOWN , SDLK_KP6 );
    AddEvent( SDL_KEYUP , SDLK_KP6 );
    break;

  case k9: //	reset
    AddEvent( SDL_KEYDOWN , SDLK_F11 );
    AddEvent( SDL_KEYUP , SDLK_F11 );
    break;

    /* --- */

  case kUp:
    AddEvent( SDL_KEYDOWN , SDLK_UP );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_UP );

    //            AddRemoteKeyList( SDLK_UP );
    break;

  case kDown:
    AddEvent( SDL_KEYDOWN , SDLK_DOWN );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_DOWN );
    //            AddRemoteKeyList( SDLK_DOWN );
    break;

  case kLeft:
    AddEvent( SDL_KEYDOWN , SDLK_LEFT );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_LEFT );
    //            AddRemoteKeyList( SDLK_LEFT );
    break;

  case kRight:
    AddEvent( SDL_KEYDOWN , SDLK_RIGHT );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_RIGHT );
    //            AddRemoteKeyList( SDLK_RIGHT );
    break;

  case kRed:
    AddEvent( SDL_KEYDOWN , SDLK_RETURN );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_RETURN );
    //            AddRemoteKeyList( SDLK_RETURN );
    break;

  case kGreen:
    AddEvent( SDL_KEYDOWN , SDLK_RSHIFT );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_RSHIFT );
    //            AddRemoteKeyList( SDLK_RSHIFT );
    break;

  case kYellow:
    AddEvent( SDL_KEYDOWN , SDLK_s );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_s );
    //            AddRemoteKeyList( SDLK_x );
    break;

  case kBlue:
    AddEvent( SDL_KEYDOWN , SDLK_d );
    usleep(50000);
    AddEvent( SDL_KEYUP , SDLK_d );
    //            AddRemoteKeyList( SDLK_w );
    break;

  case kNone:
    /*            while( m_lastRemoteKeyRead != m_lastRemoteKeyWrite )
                {
                    AddEvent( SDL_KEYUP , m_lastRemoteKey[m_lastRemoteKeyRead] );
                    m_lastRemoteKeyRead ++ ;
                    if(m_lastRemoteKeyRead >= NBMAXKEYREMOTE)
                        m_lastRemoteKeyRead = 0;
                }
    */
    break;

  default:
    break;

  }

  return state;
}

eOSState cgbcOsd::ProcessKeyGbPalMode(eKeys keys)
{
  eOSState state;

  state = osContinue;

  return state;
}

/*
        Tv / Gb / GbPal
        ok : Tv <->Gb
 
        ModeTv  :
                kOk -> ModeGb
                Vol
                Prog
        ModeGb  :
        		kOk -> ModeTv
                kBack  : SDLK_ESCAPE
                k2.  : Mode GbPal
                KYellow : pause
                KRed : SaveMovie
                KGreen : ReadMovie
                KBlue: SDLK_F12 saveBMP
                8 : SDLK_F8:        rb_on=1;
                0 : SDLK_F10    // F10: show fps
                1 : SDLK_KP6:
                9 : SDLK_F11:emu_reset();set_message("Reset");break; // F11: Reset
        ModeGbPal
                kOk -> ModeGb
                kBack  -> ModeGb
                1 case SDLK_KP1: Set Pal 1");
                2 case SDLK_KP2: Set Pal 2");
                3 case SDLK_KP3: Set Pal 3");
                4 case SDLK_KP4: Set Pal 4");
                5  case SDLK_KP5: Set Pal 5");
 
 
 
      case SDLK_ESCAPE: conf.gb_done=1;break;
      case SDLK_F10    // F10: show fps
      case SDLK_F11:emu_reset();set_message("Reset");break; // F11: Reset
      case SDLK_F12:           SDL_SaveBMP(gb_screen,"./gngb.bmp");
      case SDLK_KP1: Set Pal 1");
      case SDLK_KP2: Set Pal 2");
      case SDLK_KP3: Set Pal 3");
      case SDLK_KP4: Set Pal 4");
      case SDLK_KP5: Set Pal 5");
      case SDLK_KP6:
      case SDLK_F8:        rb_on=1;
      case SDLK_F7:         pause=1;
      case SDLK_F6:         pause=0;
      case SDLK_F4: save_movie();
      case SDLK_F5:  play_movie();
      case SDLK_TAB:                save_gb_screen();
*/

void cgbcOsd::Savegbc(void)
{
  /*int i;
  char buf[300];
  FILE *file;*/
  //
  //  f[0]=0;
  //  strcat(f,a);
  //  strcat(f,"/gngbSave/");
  //  check_dir(f);
  /*  strcpy(buf,rom_name);
    strcat(f,".sv");
   
    file = fopen(buf,"w");
    if( file != NULL )
    {
        fwrite("gngbSv",sizeof(char),6,file);
        fwrite(&nb_ram_page,sizeof(unsigned int),1,file);
   
        for(i=0;i<nb_ram_page;i++)
          fwrite(ram_page[i],sizeof(Uint8),0x2000,file);
   
        for(i=0;i<5;i++)
          fwrite(rom_timer->reg[i],sizeof(),1,file);
   
        fclose(file);
    }
  */
}
void cgbcOsd::AddRemoteKeyList(int key)
{
  m_lastRemoteKey[m_lastRemoteKeyWrite] = key;
  m_lastRemoteKeyWrite ++ ;
  if(m_lastRemoteKeyWrite >= NBMAXKEYREMOTE)
    m_lastRemoteKeyWrite = 0;
}
