/*---------------------------------------------------------------------------*
  Project:  NitroSDK - MB - demos - cloneboot
  File:     parent.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: parent.c,v $
  Revision 1.2  2004/11/24 11:07:38  yosizaki
  add parent_begin/parent_end.

  Revision 1.1  2004/11/15 06:21:18  yosizaki
  initial update.

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


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

#include "mbp.h"
#include "common.h"
#include "disp.h"
#include "gmain.h"
#include "wh.h"


/******************************************************************************/

static void GetChannelMain( void );
static BOOL ConnectMain( u16 tgid );
static void PrintChildState( void );
static BOOL JudgeConnectableChild( WMStartParentCallback* cb );


//============================================================================
//  Constant definitions
//============================================================================

/* GGID used in this demo */
#define WH_GGID                 SDK_MAKEGGID_SYSTEM(0x22)


/* Program info downloaded by this demo  */
const MBGameRegistry mbGameList = 
{
    /*
     * With a clone boot, specify NULL as the program path name.
     * However, this is specified in MBP_RegistFile() of the MBP module and 
     * it does not matter what is used as an argument in MB_RegisterFile(). 
     */
    NULL,
    (u16*)L"DataShareDemo",     // Game name 
    (u16*)L"DataSharing demo(cloneboot)",   // Description of game 
    "/data/icon.char",          // Icon character data  
    "/data/icon.plt",           // Icon palette data 
    WH_GGID,                    // GGID
    MBP_CHILD_MAX + 1,          // Max number of players including parent machine 
} ;



//============================================================================
//   Variable definitions
//============================================================================

static s32      gFrame;                 // Frame counter

//-----------------------
// For holding the communications channel 
//-----------------------
static u16 sChannel = 0;
static const MBPChildInfo* sChildInfo[ MBP_CHILD_MAX ];

//============================================================================
//   Function definitions
//============================================================================


/*****************************************************************************/
/* Start definition range of area reserved for parent (.parent section) */
#include <nitro/parent_begin.h>
/*****************************************************************************/


/*---------------------------------------------------------------------------*
  Name:         ParentMain

  Description:  Parent main routine 

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void ParentMain( void )
{
    u16 tgid = 0;
    
    // Initialize screen and OS 
    CommonInit();
    // Initialize screen 
    DispInit();
    // Initialize heap 
    InitAllocateSystem();
    
    // Set info in WH
    WH_SetGgid(WH_GGID);

    // Enable interrupts
    (void)OS_EnableIrq();
    (void)OS_EnableInterrupts();
    
    DispOn();
    
    while ( TRUE ) 
    {
        SVC_WaitVBlankIntr();
        
        // Find channel that doesn't have much traffic
        GetChannelMain();
        
        /*
         * Normally, the tgid value will change each time the parent starts. 
         * However, when reconnecting with a child using multiboot, if you 
         * do not boot with same tgid and rescan, the connection will no longer work. 
        

         * If you reconnect after rescanning, there is no need to save the tgid.
         */
        // Multiboot distribution processing
        if ( ConnectMain( ++tgid ) )
        {
            // Multiboot child started successfully
            break;
        }
    }
    
    //--------------
    // After a multiboot, the child machine is reset and communications are cut. 
    // Currently, the parent machine must terminate the connection from MB_End(). 
    // After cutting communications between the parent and child, establish communication from scratch.
    // 
    // At this time, the aids of the child are shuffled. If necessary, 
    // before a multiboot, save the aids and MAC addresses,
    //  and when reconnecting make a connection with the new aid. 
    //--------------
    
    
    // Set the buffer for data sharing communications 
    GInitDataShare();
    
#if !defined(MPB_USING_MB_EX)
    (void)WH_Initialize();
#endif

    // Set the functions used to determine the connectability of the child 
        WH_SetJudgeAcceptFunc( JudgeConnectableChild );

    /* Main routine */
    for ( gFrame = 0 ; TRUE ; gFrame++ )
    {
        SVC_WaitVBlankIntr();
        
        ReadKey();
        
        BgClear();
        
        switch ( WH_GetSystemState() )
        {
        case WH_SYSSTATE_IDLE :
            /* ----------------
             * When a child connects to the same parent without rescanning, 
             * The parent's tgid and channel must match the child's. 
             * In this demo, the parent and child both use the channel used during multiboot
             * and the tgid used during multiboot +1, so they can connect without rescanning. 
             * 
             * When specifying the MAC address, you can rescan without having
             * the same tgids and channels. 
             * ---------------- */
            (void)WH_ParentConnect( WH_CONNECTMODE_DS_PARENT, (u16)(tgid + 1), sChannel );
            break;
            
        case WH_SYSSTATE_CONNECTED:
        case WH_SYSSTATE_KEYSHARING:
        case WH_SYSSTATE_DATASHARING:
            {
                BgPutString( 8 , 1 , 0x2 , "Parent mode" );
                GStepDataShare( gFrame );
                GMain();
            }           
            break;
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         GetChannelMain

  Description:  Checks the usage level of channels and requests one. 

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void GetChannelMain( void )
{
    
    /*-----------------------------------------------*
     * Checks carefully the signal usage of the channels and 
     * and selects the one that is least busy. 
     * To run WM_MeasureChannel() the machine must be in the idle state and 
     * when multibooting, the machine cannot stay in the idle state
     * and thus this option cannot be used. 
     * Call WM_Initialize and after checking the traffic level, terminate with WM_End
     * and call MB_Init again. 
     *-----------------------------------------------*/
    (void)WH_Initialize();
    
    while ( TRUE )
    {
        ReadKey();
        BgClear();
        BgSetMessage( PLTT_YELLOW, " Search Channel " );
        
        switch ( WH_GetSystemState() )
        {
        //-----------------------------------------
        // Compete initialization 
        case WH_SYSSTATE_IDLE:
            BgSetMessage( PLTT_WHITE, " Push A Button to start   ");
            if ( IS_PAD_TRIGGER( PAD_BUTTON_A ) )
            {
                BgSetMessage( PLTT_YELLOW, "Check Traffic ratio       ");
                (void)WH_StartMeasureChannel();
            }
            break;
        //-----------------------------------------
        // Complete channel survey 
        case WH_SYSSTATE_MEASURECHANNEL:
            {
                sChannel = WH_GetMeasureChannel();
#if !defined(MPB_USING_MB_EX)
                (void)WH_End();
#else
                /* Go to multiboot processing while maintaining the Idle state */
                return;
#endif
            }
            break;
        //-----------------------------------------
        // End WM
        case WH_SYSSTATE_STOP:
            /* When WM_End finishes, go to multiboot processing */
            return;
        //-----------------------------------------
        // Busy 
        case WH_SYSSTATE_BUSY:
            break;
        //-----------------------------------------
        // Error 
        case WH_SYSSTATE_ERROR:
            (void)WH_Reset();
            break;
        //-----------------------------------------
        default:
            OS_Panic("Illegal State\n");
        }
        SVC_WaitVBlankIntr();       // Wait for V blank interrupt to finish 
    }
}


/*---------------------------------------------------------------------------*
  Name:         ConnectMain

  Description:  Connects with multiboot.

  Arguments:    tgid       The tgit used when starting as a parent.

  Returns:      If transfer to the child machine is successful, returns TRUE;
                If unsuccessful or cancelled, returns FALSE. 
 *---------------------------------------------------------------------------*/
static BOOL ConnectMain( u16 tgid )
{
    MBP_Init( mbGameList.ggid, tgid );
    
    while ( TRUE )
    {
        ReadKey();
        
        BgClear();
        
        BgSetMessage( PLTT_YELLOW, " MB Parent " );
        
        switch ( MBP_GetState() )
        {
        //-----------------------------------------
        // Idle state 
        case MBP_STATE_IDLE :
            {
                MBP_Start( &mbGameList, sChannel );
            }
            break;
        
        //-----------------------------------------
        // Receiving entry from the child 
        case MBP_STATE_ENTRY :
            {
                BgSetMessage( PLTT_WHITE, " Now Accepting            ");
                
                if ( IS_PAD_TRIGGER( PAD_BUTTON_B ) )
                {
                // Cancel multiboot with the B button 
                    MBP_Cancel();
                    break;
                }
                
                // If one or more child machines are in entry, start is possible. 
                if ( MBP_GetChildBmp( MBP_BMPTYPE_ENTRY ) ) 
                {
                    BgSetMessage( PLTT_WHITE, " Push START Button to start   ");
                    
                    if ( IS_PAD_TRIGGER( PAD_BUTTON_START ) )
                    {
                        // Start download 
                        MBP_StartDownloadAll();
                    }
                }
            }
            break;
            
        //-----------------------------------------
        // Program distribution processing 
        case MBP_STATE_DATASENDING :
            {
                
                // If all are finished downloading, start is possible. 
                if ( MBP_IsBootableAll() )
                {
                    // Start boot
                    MBP_StartRebootAll();
                }
            }
            break;
        
        //-----------------------------------------
        // Reboot processing
        case MBP_STATE_REBOOTING:
            {
                BgSetMessage( PLTT_WHITE, " Rebooting now                ");
            }
            break;
        
        //-----------------------------------------
        // Reconnection processing
        case MBP_STATE_COMPLETE :
            {
                // If all machines connect properly, end multiboot processing 
                // Restart wireless as normal parent.
                BgSetMessage( PLTT_WHITE, " Reconnecting now             ");
                
                SVC_WaitVBlankIntr();
                return TRUE;
            }
            break;
        
        //-----------------------------------------
        // Error occurred
        case MBP_STATE_ERROR:
            {
            // Cancel communication 
                MBP_Cancel();
            }
            break;
        
        //-----------------------------------------
        // Busy cancelling communication
        case MBP_STATE_CANCEL:
            // None
            break;
        
        //-----------------------------------------
        // Finish error 
        case MBP_STATE_STOP:
            SVC_WaitVBlankIntr();
            return FALSE;
        }
        
        // Display child state 
        PrintChildState();
        
        SVC_WaitVBlankIntr();       // Wait for end of V blank interrupt 
    }
}


/*---------------------------------------------------------------------------*
  Name:         PrintChildState

  Description:  Displays child info on screen 

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void PrintChildState( void )
{
    static const char* STATE_NAME[] = 
    {
        "NONE       ",
        "CONNECTING ",
        "REQUEST    ",
        "ENTRY      ",
        "DOWNLOADING",
        "BOOTABLE   ",
        "BOOTING    ",
    };
    enum 
    {
        STATE_DISP_X = 2,
        INFO_DISP_X  = 15,
        BASE_DISP_Y  = 2
    };
    
    u16 i;
    
    /* Display child list */
    for ( i = 1; i <= MBP_CHILD_MAX; i++ )
    {
        const MBPChildInfo* childInfo;
        MBPChildState childState = MBP_GetChildState( i );
        const u8* macAddr;
        
        SDK_ASSERT( childState < MBP_CHILDSTATE_NUM );
        
        // Display state
        BgPutString( STATE_DISP_X, i + BASE_DISP_Y, PLTT_WHITE, STATE_NAME[ childState ] );
        
        // Display user info 
        childInfo = MBP_GetChildInfo( i );
        macAddr = MBP_GetChildMacAddress( i );
        
        if ( macAddr != NULL )
        {
            BgPrintStr( INFO_DISP_X, i + BASE_DISP_Y, PLTT_WHITE, 
                        "%02x%02x%02x%02x%02x%02x",
                        macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5] );
        }
    }
}


/*---------------------------------------------------------------------------*
  Name:         JudgeConnectableChild

  Description:  Determines if child is connectable during reconnect

  Arguments:    cb      Info about the incoming child.

  Returns:      If connectable TRUE.
                If not connectable FALSE.
 *---------------------------------------------------------------------------*/
static BOOL JudgeConnectableChild( WMStartParentCallback* cb )
{
    u16 playerNo;
    
    /*  Search for the child's multiboot AID (cb->aid) from the MAC address */
    playerNo = MBP_GetPlayerNo( cb->macAddress );
    
    OS_TPrintf( "MB child(%d) -> DS child(%d)\n", playerNo, cb->aid );
    
    if ( playerNo == 0 )
    {
        return FALSE;
    }
    
    sChildInfo[ playerNo ] = MBP_GetChildInfo( playerNo );
    return TRUE;
}


/*****************************************************************************/
/* End definition range of area reserved for parent (.parent section) */
#include <nitro/parent_end.h>
/*****************************************************************************/
