#include <stdio.h>
#include <stdlib.h>
#include <config.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/joystick.h>
#include <pthread.h>
#include <semaphore.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


#include "osd.h"
#include "thread.h"
#include "plugin.h"
// #include "device.h"
// #include "status.h"

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

#include "gbcosd.h"


#ifdef  __cplusplus
extern "C"
{
#endif

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

    int mainVDR( char *argv );

    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;

}

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;

    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 );
}

cgbcOsd::cgbcOsd(  )
{
    eOsdError Result;

    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_pOsd = NULL;

    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 );

}

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");

    if ( m_pOsd == NULL )
    {
        m_pOsd = cOsdProvider::NewOsd(50,260);

        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);
        }
        Start();
    }
}

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;
}
