/*---------------------------------------------------------------------------*
  Project:  NitroSDK - WM - demos - simple-1
  File:     main.c

  Copyright 2003,2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: main.c,v $
  Revision 1.8  2004/10/22 11:06:39  terui
  wc module cut to wireless shared region, supporting change in I/F.

  Revision 1.7  2004/08/30 04:06:37  seiki_masashi
  Small fix.

  Revision 1.6  2004/08/30 02:41:19  seiki_masashi
  Small fix.

  Revision 1.5  2004/08/30 01:56:53  seiki_masashi
  Changed so Port receive callback is used

  Revision 1.4  2004/08/23 07:27:27  yasu
  Workaround for pushing A button in IPL

  Revision 1.3  2004/08/17 12:39:57  seiki_masashi
  Arranged send data in 32-byte boundary.

  Revision 1.2  2004/08/02 07:11:43  terui
 Changed transmission data editing buffer to array

  Revision 1.1  2004/07/23 15:28:14  terui
  Made adjustments and new additions

  Revision 1.4  2004/07/23 14:52:05  terui
  Only fix comment

  Revision 1.3  2004/07/23 01:04:02  terui
  Adjusted code

  Revision 1.2  2004/07/20 07:46:40  terui
  Uploaded tentative version for connection testing

  Revision 1.1  2004/07/15 12:48:42  terui
  initial upload

  $NoKeywords: $
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
    A simple functional sample of wireless communication using the WM library.
    Automatically connects to nearby simple demos and delivers a 4-byte frame
    count by wireless communication.

    HOWTO:
        1.  Press button A to start communication as the parent.
			When a nearby child with the same simple demo is found,
			communication with the child  automatically begins.  Communication
			with up to 15 child devices is possible at the same time.
        2. Press button B to start communication as a child.  When a nearby parent
        	with the same simple demo is found, communication with that parent
        	automatically begins.
        3. f the START button is pressed during a 'busy' screen, while a connection
			is being established, or during a 'communication' screen, after a connection is established,
        	the connection is reset, and the display returns to the initial screen.
 *---------------------------------------------------------------------------*/

#include    <nitro.h>
#include    <nitro/wm.h>

#include    "font.h"
#include    "wc.h"


/*---------------------------------------------------------------------------*
    Constant definitions
 *---------------------------------------------------------------------------*/
#define     KEY_REPEAT_START    25      // Number of frames to start of key repeat
#define     KEY_REPEAT_SPAN     10      // Number of frames in key repeat interval

#define     PICTURE_FRAME_PER_GAME_FRAME    1

#define     DEFAULT_GGID            0x003fff10
#define     NUM_MAX_CHILD           15
#define     DEFAULT_CHAN            1
#define     PARENT_DATA_SIZE        4
#define     CHILD_DATA_SIZE         4

static const WMParentParam   defaultParameter =
{
    NULL ,  0 , 0 ,
    DEFAULT_GGID ,              // Game group ID
    0x0000 ,                    // temporary ID (incremented by 1 each time)
    1 ,                         // Entry permission flag
    NUM_MAX_CHILD ,             // Maximum number of connected children
    0 ,                         // Multiboot flag
    0 ,                         // Key sharing
    0 ,                         // Continuous send flag
    200 ,                       // beacon interval
    {   0 , 0 , 0 , 0   } ,     // User name
    {   0 , 0 , 0 , 0 , 0 , 0 , 0 , 0   } , // Game name
    DEFAULT_CHAN ,              // Connect channel
    PARENT_DATA_SIZE ,          // Parent send data size
    CHILD_DATA_SIZE             // Child send data size
};

/*---------------------------------------------------------------------------*
    Structure definition
 *---------------------------------------------------------------------------*/
// Key input information
typedef struct KeyInfo
{
    u16     cnt;    // Unprocessed input values
    u16     trg;    // Press trigger input
    u16     up;     // Release trigger input
    u16     rep;    // Sustained press repeat input

} KeyInfo;


/*---------------------------------------------------------------------------*
    Internal function definitions
 *---------------------------------------------------------------------------*/
static void     ModeSelect( void );     // Parent/child selection screen
static void     ModeError( void );      // Error display screen
static void     ModeWorking( void );    // Busy screen
static void     ModeParent( void );     // Parent communication screen
static void     ModeChild( void );      // Child communication screen
static void     VBlankIntr( void );     // V-blank interrupt handler

// Functions called when receiving data
static void     MpReceiveCallback( u16 aid , u16* data , u16 length );

// General-purpose subroutines
static void     KeyRead( KeyInfo* pKey );
static void     ClearString( void );
static void     PrintString( s16 x , s16 y , u8 palette , char* text , ... );
static void     ColorString( s16 x , s16 y , s16 length , u8 palette );
static void     InitializeAllocateSystem( void );


/*---------------------------------------------------------------------------*
    Internal variable definitions
 *---------------------------------------------------------------------------*/
static u16      gScreen[ 32 * 32 ];     // Virtual screen
static KeyInfo  gKey;                   // Key input
static s32      gFrame;                 // Frame counter

// Send/receive buffer for display
static u32      gSendBuf[ 1024 ] ATTRIBUTE_ALIGN(32);
static u32      gRecvBuf[ 1 + WM_NUM_MAX_CHILD ][ 1024 ];
static BOOL     gRecvFlag[ 1 + WM_NUM_MAX_CHILD ];


/*---------------------------------------------------------------------------*
  Name:         NitroMain

  Description:  Initialization and main loop

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
NitroMain( void )
{
    // Various types of initialization
    OS_InitPrintServer();
    OS_Init();
    FX_Init();
    GX_Init();
    PXI_Init();
    GX_DispOff();
    GXS_DispOff();

    // Initialize display settings
    GX_SetBankForLCDC( GX_VRAM_LCDC_ALL );
    MI_CpuClearFast( (void*)HW_LCDC_VRAM , HW_LCDC_VRAM_SIZE );
    (void)GX_DisableBankForLCDC();
    MI_CpuFillFast( (void*)HW_OAM,192 , HW_OAM_SIZE );
    MI_CpuClearFast( (void*)HW_PLTT , HW_PLTT_SIZE );
    MI_CpuFillFast( (void*)HW_DB_OAM , 192,HW_DB_OAM_SIZE );
    MI_CpuClearFast( (void*)HW_DB_PLTT , HW_DB_PLTT_SIZE );

    //2D display settings for displaying strings
    GX_SetBankForBG( GX_VRAM_BG_128_A );
    G2_SetBG0Control(
        GX_BG_SCRSIZE_TEXT_256x256 ,
        GX_BG_COLORMODE_16 ,
        GX_BG_SCRBASE_0xf800 ,      // SCR base block 31
        GX_BG_CHARBASE_0x00000 ,    // CHR base block 0
        GX_BG_EXTPLTT_01
    );
    G2_SetBG0Priority( 0 );
    G2_BG0Mosaic( FALSE );
    GX_SetGraphicsMode( GX_DISPMODE_GRAPHICS , GX_BGMODE_0,GX_BG0_AS_2D );
    GX_SetVisiblePlane( GX_PLANEMASK_BG0 );
    GX_LoadBG0Char( d_CharData , 0 , sizeof( d_CharData ) );
    GX_LoadBGPltt( d_PaletteData , 0 , sizeof( d_PaletteData ) );
    MI_CpuFillFast( (void*)gScreen , 0 , sizeof( gScreen ) );
    DC_FlushRange( gScreen , sizeof( gScreen ) );
    GX_LoadBG0Scr( gScreen , 0 , sizeof( gScreen ) );

    // Interrupt settings
    OS_SetIrqFunction( OS_IE_V_BLANK , VBlankIntr );
    (void)OS_EnableIrqMask( OS_IE_V_BLANK );
    (void)GX_VBlankIntr( TRUE );
    (void)OS_EnableIrq();
    (void)OS_EnableInterrupts();

    // Memory allocation
    InitializeAllocateSystem();

    //********************************
    // Initialize wireless
    WcInit(
        &defaultParameter ,
        FALSE ,
        FALSE ,
        PICTURE_FRAME_PER_GAME_FRAME
    );
    WcSetMpReceiveCallback( MpReceiveCallback );
    //********************************

    // Start LCD display
    GX_DispOn();
    GXS_DispOn();

    // Debugging string output
    OS_Printf("ARM9: WM simple demo started.\n");

    // empty call that gets key input information (push A button in IPL)
    KeyRead( &gKey );

    // Main loop
    for( gFrame = 0 ; TRUE ; gFrame ++ )
    {
        // Get key input information
        KeyRead( &gKey );

        // Clear screen
        ClearString();

        // Determine processing based on communication status
        switch( WcGetStatus() )
        {
        case WC_STATUS_READY:
            ModeSelect();
            break;
        case WC_STATUS_ERROR:
            ModeError();
            break;
        case WC_STATUS_BUSY:
            ModeWorking();
            break;
        case WC_STATUS_PARENT:
            ModeParent();
            break;
        case WC_STATUS_CHILD:
            ModeChild();
            break;
        }

        // Wait for v-blank
        SVC_WaitVBlankIntr();
        // Print ARM7 debugging output
        OS_PrintServer();
    }
}

/*---------------------------------------------------------------------------*
  Name:         ModeSelect

  Description:  Processing for parent/child selection screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeSelect( void )
{
    PrintString( 3 , 10 , 0xf , "Push A to connect as PARENT" );
    PrintString( 3 , 12 , 0xf , "Push B to connect as CHILD" );

    if( gKey.trg & PAD_BUTTON_A )
    {
        //********************************
        s32     i;

        for( i = 1 ; i < ( WM_NUM_MAX_CHILD + 1 ) ; i ++ )
        {
            gRecvFlag[ i ] = FALSE;
        }

        WcStartParent();
        //********************************
    }
    else if( gKey.trg & PAD_BUTTON_B )
    {
        //********************************
        WcStartChild();
        //********************************
    }
}

/*---------------------------------------------------------------------------*
  Name:         ModeError

  Description:  Processing for error display screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeError( void )
{
    PrintString( 5 , 10 , 0x1 , "======= ERROR! =======" );
    PrintString( 5 , 13 , 0xf , " Fatal error occured." );
    PrintString( 5 , 14 , 0xf , "Please reboot program." );
}

/*---------------------------------------------------------------------------*
  Name:         ModeWorking

  Description:  Processing for busy screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeWorking( void )
{
    PrintString( 9 , 11 , 0xf , "Now working..." );

    if( gKey.trg & PAD_BUTTON_START )
    {
        //********************************
        WcEnd();
        //********************************
    }
}

/*---------------------------------------------------------------------------*
  Name:         ModeParent

  Description:  Processing for parent communication screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeParent( void )
{
    PrintString( 8 , 1 , 0x2 , "Parent mode" );
    PrintString( 4 , 3 , 0x4 , "Send:     %08X" , gSendBuf[ 0 ] );
    PrintString( 4 , 5 , 0x4 , "Receive:" );
    {
        s32     i;

        for( i = 1 ; i < ( WM_NUM_MAX_CHILD + 1 ) ; i ++ )
        {
            if( gRecvFlag[ i ] )
            {
                PrintString( 5 , (s16)( 6 + i ) , 0x4 , "Child%02d: %08X" , i , gRecvBuf[ i ][ 0 ] );
            }
            else
            {
                PrintString( 5 , (s16)( 6 + i ) , 0x7 , "No child" );
            }
        }
    }

    if( gKey.trg & PAD_BUTTON_START )
    {
        //********************************
        WcEnd();
        //********************************
    }
}

/*---------------------------------------------------------------------------*
  Name:         ModeChild

  Description: Processing for child communication screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeChild( void )
{
    PrintString( 8 , 1 , 0x2 , "Child mode" );
    PrintString( 4 , 3 , 0x4 , "Send:     %08X" , gSendBuf[ 0 ] );
    PrintString( 4 , 5 , 0x4 , "Receive:" );
    PrintString( 5 , 7 , 0x4 , "Parent:  %08X" , gRecvBuf[ 0 ][ 0 ] );

    if( gKey.trg & PAD_BUTTON_START )
    {
        //********************************
        WcEnd();
        //********************************
    }
}

/*---------------------------------------------------------------------------*
  Name:         VBlankIntr

  Description:  V-blank interrupt vector

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
VBlankIntr( void )
{
    // Start new data transmission
    switch( WcGetStatus() )
    {
    case WC_STATUS_PARENT:
        gSendBuf[ 0 ] = gFrame;
        //********************************
        WcSetParentData( gSendBuf , PARENT_DATA_SIZE );
        //********************************
        break;
    case WC_STATUS_CHILD:
        gSendBuf[ 0 ] = gFrame;
        //********************************
        WcSetChildData( gSendBuf , CHILD_DATA_SIZE );
        //********************************
        break;
    }

    // Reflect virtual screen in VRAM
    DC_FlushRange( gScreen , sizeof( gScreen ) );
    GX_LoadBG0Scr( gScreen , 0 , sizeof( gScreen ) );

    // Set IRQ check flag
    OS_SetIrqCheckFlag( OS_IE_V_BLANK );
}

/*---------------------------------------------------------------------------*
  Name:         MpReceiveCallback

  Description:  Function called when MP data is received

  Arguments:    aid     - The aid of a child that originates a send (in the case of 0, data is from a parent)
                data    - Pointer to received data (NULL for disconnect notification)
                length  - Size of received data

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
MpReceiveCallback( u16 aid , u16* data , u16 length )
{
#pragma unused( length )
    SDK_MAX_ASSERT( aid , 15 );

    if( data != NULL )
    {
        SDK_ASSERT( length >= 4 );
        gRecvFlag[ aid ] = TRUE;
        // Copy origin is 2 byte aligned (not 4-byte aligned)
        if( aid == 0 )
        {
            // In the case of receiving from a parent
            MI_CpuCopy8( data , &( gRecvBuf[ aid ][ 0 ] ) , PARENT_DATA_SIZE );
        }
        else
        {
            MI_CpuCopy8( data , &( gRecvBuf[ aid ][ 0 ] ) , CHILD_DATA_SIZE );
        }
    }
    else
    {
        gRecvFlag[ aid ] = FALSE;
    }
}

/*---------------------------------------------------------------------------*
  Name:         KeyRead

  Description:  Edits key input information
                Detects trigger press, trigger release, and sustained trigger repeat

  Arguments:    pKey  - Structure for key input information to be edited

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
KeyRead( KeyInfo* pKey )
{
    static u16  repeat_count[12];
    int         i;
    u16         r;

    r = PAD_Read();
    pKey->trg = 0x0000;
    pKey->up = 0x0000;
    pKey->rep = 0x0000;

    for( i = 0 ; i < 12 ; i ++ )
    {
        if( r & ( 0x0001 << i ) )
        {
            if( !( pKey->cnt & ( 0x0001 << i ) ) )
            {
                pKey->trg |= ( 0x0001 << i );       // Trigger press
                repeat_count[ i ] = 1;
            }
            else
            {
                if( repeat_count[ i ] > KEY_REPEAT_START )
                {
                    pKey->rep |= ( 0x0001 << i );       // Sustained press repeat
                    repeat_count[ i ] = KEY_REPEAT_START - KEY_REPEAT_SPAN;
                }
                else
                {
                    repeat_count[ i ] ++;
                }
            }
        }
        else
        {
            if( pKey->cnt & ( 0x0001 << i ) )
            {
                pKey->up |= ( 0x0001 << i );        // Trigger release
            }
        }
    }
    pKey->cnt = r;      // Unprocessed key input
}

/*---------------------------------------------------------------------------*
  Name:         ClearString

  Description:  Clears the virtual screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ClearString(void)
{
    MI_CpuClearFast( (void*)gScreen , sizeof( gScreen ) );
}

/*---------------------------------------------------------------------------*
  Name:         PrintString

  Description:  Arranges strings on virtual screen; strings up to 32 characters

  Arguments:    x       - x-coordinate for beginning of string (x 8 dots).
                y       - y-coordinate for beginning of string (x 8 dots)
                palette - Specify character color using palette number
                text    - String to be arranged on screen; terminated by null character
                ...     - Virtual argument

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
PrintString( s16 x , s16 y , u8 palette , char* text , ... )
{
    va_list vlist;
    char    temp[ 32 + 2 ];
    s32     i;

    va_start( vlist , text );
    (void)vsnprintf( temp , 33 , text , vlist );
    va_end( vlist );

    *(u16*)( &temp[ 32 ] ) = 0x0000;
    for( i = 0 ; ; i ++ )
    {
        if( temp[ i ] == 0x00 )
        {
            break;
        }
        gScreen[ ( ( y * 32 ) + x + i ) % ( 32 * 32 ) ] = (u16)(
            ( palette << 12 ) | temp[ i ]
        );
    }
}

/*---------------------------------------------------------------------------*
  Name:         ColorString

  Description:  Changes color of string arranged on virtual screen

  Arguments:    x       - x-coordinate for start of color change (x 8 dots)
                y       - y-coordinate for start of color change (x 8 dots)
                length  - Number of consecutive characters for color change
                palette - Specify character color using palette number

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ColorString( s16 x , s16 y , s16 length , u8 palette )
{
    s32     i;
    u16     temp;
    s32     index;

    if( length < 0 ) return;

    for( i = 0 ; i < length ; i ++ )
    {
        index = ( ( y * 32 ) + x + i ) % ( 32 * 32 );
        temp = gScreen[ index ];
        temp &= 0x0fff;
        temp |= ( palette << 12 );
        gScreen[ index ] = temp;
    }
}

/*---------------------------------------------------------------------------*
  Name:         InitializeAllocateSystem

  Description:  Initializes system by allocating memory using arena in main memory

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void InitializeAllocateSystem(void)
{
    void*           tempLo;
    OSHeapHandle    hh;

    // Assumes OS_Init is called
    tempLo = OS_InitAlloc(
        OS_ARENA_MAIN ,
        OS_GetMainArenaLo() ,
        OS_GetMainArenaHi() ,
        1
    );
    OS_SetArenaLo(
        OS_ARENA_MAIN ,
        tempLo
    );
    hh = OS_CreateHeap(
        OS_ARENA_MAIN ,
        OS_GetMainArenaLo() ,
        OS_GetMainArenaHi()
    );
    if( hh < 0 )
    {
        OS_Panic("ARM9: Fail to create heap...\n");
    }
    hh = OS_SetCurrentHeap(
        OS_ARENA_MAIN ,
        hh
    );
}

/*---------------------------------------------------------------------------*
  End of file
 *---------------------------------------------------------------------------*/
