/*---------------------------------------------------------------------------*
  Project:  NitroSDK - MB - libraries
  File:     mb_child.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: mb_measure_channel.c,v $
  Revision 1.5  2004/10/22 04:10:30  takano_makoto
  Changed the name of MBM_MeasureChannelEx to MBM_MeasureChannelInIdle

  Revision 1.4  2004/10/22 02:54:54  takano_makoto
  Made changes so that the state is cleared before the user callback

  Revision 1.3  2004/10/22 02:39:57  takano_makoto
  Added MBM_MeasureChannelEx

  Revision 1.2  2004/09/28 09:52:44  takano_makoto
  Added debug print

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

#include <nitro/wm.h>

#include "mb_measure_channel.h"

#undef MBM_DEBUG

#ifdef MBM_DEBUG
#define PRINTF OS_TPrintf
#else
#define PRINTF(...) ((void)0)
#endif

#define MBM_MEASURE_DMA_NO                 2

enum 
{
    MBM_STATE_INIT,        // Initial state
    MBM_STATE_MEASURING,   // Start state
    MBM_STATE_END_MEASURE, // End measurement
    MBM_STATE_END,         // Wireless OFF state
    MBM_STATE_ERR          // Error state
};


enum
{
    ERRCODE_SUCCESS = 0,    // Success
    ERRCODE_NOMORE_CHANNEL, // No more channels were found beyond this
    ERRCODE_API_ERR         // WM API execution error
};

//===========================================================================
// Declaration of variables
//===========================================================================

static MBMCallbackFunc sUserCallbackFunc = NULL;
static int mbm_measure_state = MBM_STATE_END;
// For communication channel storage
static s16 sChannel;
static u16 sChannelBusyRatio;

static BOOL sUseInIdle = FALSE;


//===========================================================================
// Declaration of function prototypes
//===========================================================================

static int  wmi_measure_channel( WMCallbackFunc callback, u16 channel );
static void wm_callback( void *arg );
static void start_measure_channel( void );
static u16  state_in_measure_channel( u16 channel );
static void state_out_measure_channel( void *arg );
static void state_in_wm_end( void );
static void user_callback( s16 num );
static void change_mbm_state( u16 state );
static void callback_ready( s16 result );

//===========================================================================
// Definition of functions
//===========================================================================

/* ----------------------------------------------------------------------
  Check the radio usage rate
  ---------------------------------------------------------------------- */
static inline int wmi_measure_channel( WMCallbackFunc callback, u16 channel )
{
#define MBM_MEASURE_TIME         30          // The interval (in ms) of only the radio communicating once per frame that can be picked up
#define MBM_MEASURE_CS_OR_ED     3           // Logical OR of the carrier sense and the ED value
#define MBM_MEASURE_ED_THRESHOLD 17          // The recommended ED threshold considered empirically valid by experimental data

    /*
     * An experimental value considered to be empirically valid has been used 
     * as a parameter for obtaining the radio usage rate.
     */
    return WM_MeasureChannel( callback,                   // callback settings
                              MBM_MEASURE_CS_OR_ED,       // CS or ED
                              MBM_MEASURE_ED_THRESHOLD,   // Invalid when only carrier sense
                              channel,                    // One of the channels obtained with WM_GetAllowedChannel
                              MBM_MEASURE_TIME );         // The search time for 1 channel (in ms)
}



/*---------------------------------------------------------------------------*
  Name:         MBM_MeasureChannel

  Description:  Searches for the channel with the lowest usage rate.
                Call from a wireless OFF state.
                Returns a callback after internally measuring the radio and setting it to a wireless OFF state.

  Arguments:    wm_buffer   WM Work Memory
                callback    Designates the user callback to call when the search has finished

  Returns:      None.
 *---------------------------------------------------------------------------*/
void MBM_MeasureChannel( u8 *wm_buffer, MBMCallbackFunc callback )
{
    sUseInIdle = FALSE;
    sUserCallbackFunc = callback;
    
    // Begin initialization sequence.
    if ( mbm_measure_state != MBM_STATE_END )
    {
        user_callback( MBM_MEASURE_ERROR_ILLEGAL_STATE );
        return;
    }
    
    if ( WM_Initialize( wm_buffer, wm_callback, MBM_MEASURE_DMA_NO ) 
         != WM_ERRCODE_OPERATING )
    {
        user_callback( MBM_MEASURE_ERROR_INIT_API );
        return;
    }
}

/*---------------------------------------------------------------------------*
  Name:         MBM_MeasureChannelInIdle

  Description:  Searches for the channel with the lowest usage rate.
                Call from an idle state.
                Returns a callback after internally measuring and setting the radio to the IDLE state.

  Arguments:    callback    Designates the user callback to call when the search has finished

  Returns:      None.
 *---------------------------------------------------------------------------*/
void MBM_MeasureChannelInIdle( MBMCallbackFunc callback )
{
    sUseInIdle = TRUE;
    sUserCallbackFunc = callback;
    
    // Begin initialization sequence
    if ( mbm_measure_state != MBM_STATE_END )
    {
        user_callback( MBM_MEASURE_ERROR_ILLEGAL_STATE );
        return;
    }
    
    start_measure_channel();
}

/* ----------------------------------------------------------------------
  WM calback
  ---------------------------------------------------------------------- */
static void wm_callback( void *arg )
{
    WMCallback *cb = (WMCallback*)arg;
    
    switch ( cb->apiid ) 
    {
    case WM_APIID_INITIALIZE:
    /* WM_Initialize callback */
        {
            WMCallback* cb  = (WMCallback*)arg;
            if (cb->errcode != WM_ERRCODE_SUCCESS) 
            {
                user_callback( MBM_MEASURE_ERROR_INIT_CALLBACK );
                return;
            }
        }
        change_mbm_state( MBM_STATE_INIT );
        start_measure_channel();
        break;
        
    case WM_APIID_MEASURE_CHANNEL:
    /* WM_MeasureChannel callback */
        state_out_measure_channel( arg );
        break;
        
    case WM_APIID_END:
        change_mbm_state( MBM_STATE_END );
        user_callback( sChannel );
        break;

    default:
        OS_TPanic("Get illegal callback");
        
        break;
    }
}

/* ----------------------------------------------------------------------
  Begin searching for the radio usage rate
  ---------------------------------------------------------------------- */
static void start_measure_channel( void )
{
#define MAX_RATIO 100       // The channel usage rate ranges from 1 - 100
    u16 result;
    
    sChannel = 0;
    sChannelBusyRatio = MAX_RATIO + 1;
    
    result = state_in_measure_channel( 1 ); // Check in order from channel 1
    
    if ( result == ERRCODE_NOMORE_CHANNEL )
    {
        // There is not even one usable channel
        callback_ready( MBM_MEASURE_ERROR_NO_ALLOWED_CHANNEL );
        return;
    }
    
    if ( result == ERRCODE_API_ERR )
    {
        // End error
        callback_ready( MBM_MEASURE_ERROR_MEASURECHANNEL_API );
        return;
    }
    
    // Begin measuring the radio usage rate
    change_mbm_state( MBM_STATE_MEASURING );
}


/*---------------------------------------------------------------------------*
  Name:         state_in_measure_channel

  Arguments:    channel     The number of the channel on which to start the search

  Returns:      ERRCODE_SUCCESS        - Searching
                ERRCODE_NOMORE_CHANNEL - There are no more channels to search
                ERRCODE_API_ERR        - WM_MeasureChannel API callback error
 *---------------------------------------------------------------------------*/
static u16 state_in_measure_channel( u16 channel )
{
    u16 allowedCannel;
    
    allowedCannel = WM_GetAllowedChannel();
    
    while ( ((1 << (channel - 1)) & allowedCannel) == 0 )
    {
        channel++;
        if ( channel > 16 )
        {
            /* When finished looking up all allowed channels */
            return ERRCODE_NOMORE_CHANNEL;
        }
    }
    
    if ( wmi_measure_channel( wm_callback, channel ) != WM_ERRCODE_OPERATING )
    {
        return ERRCODE_API_ERR;
    }
    return ERRCODE_SUCCESS;
}


static void state_out_measure_channel( void *arg )
{
    u16 result;
    u16 channel;
    WMMeasureChannelCallback* cb = (WMMeasureChannelCallback*)arg;

    if ( cb->errcode != WM_ERRCODE_SUCCESS )
    {
        callback_ready( MBM_MEASURE_ERROR_MEASURECHANNEL_CALLBACK );
        return;
    }
    
    channel = cb->channel;
    PRINTF("CH%d = %d\n", cb->channel, cb->ccaBusyRatio);
    
    if ( cb->ccaBusyRatio < sChannelBusyRatio )
    {
        sChannelBusyRatio = cb->ccaBusyRatio;
        sChannel = channel;
    }
    
    result = state_in_measure_channel( ++channel );
    
    if ( result == ERRCODE_NOMORE_CHANNEL )
    {
        // End channel search
        callback_ready( sChannel );
        return;
    }
    
    if ( result == ERRCODE_API_ERR )
    {
        // End error
        callback_ready( MBM_MEASURE_ERROR_MEASURECHANNEL_API );
        return;
    }
    
    // Do nothing if ERRCODE_SUCCESS
    
}


/* ----------------------------------------------------------------------
  Turn off wireless before the callback
  ---------------------------------------------------------------------- */
static void callback_ready( s16 result )
{
    sChannel = result;
    if ( sUseInIdle )
    {
        change_mbm_state( MBM_STATE_END );
        user_callback( result );
    }
    else
    {
        state_in_wm_end();
        change_mbm_state( MBM_STATE_END_MEASURE );
    }
}

/* ----------------------------------------------------------------------
  End WM
  ---------------------------------------------------------------------- */
static void state_in_wm_end( void )
{
    if ( WM_End( wm_callback ) != WM_ERRCODE_OPERATING )
    {
        OS_TPanic("fail WM_End");
    }
}


/* ----------------------------------------------------------------------
  Change the MBM internal state
  ---------------------------------------------------------------------- */
static void change_mbm_state( u16 state )
{
#ifdef MBM_DEBUG
    static const char *STATE_STR[] =
    {
        "INIT",
        "MEASURING",   // Begin state
        "END_MEASURE", // End measuring
        "END",         // Wireless OFF state
        "ERR"          // Error state
    };
#endif
    
    PRINTF("%s -> ", STATE_STR[mbm_measure_state] );
    mbm_measure_state = state;
    PRINTF("%s\n", STATE_STR[mbm_measure_state] );
}


/* ----------------------------------------------------------------------
  Call the user callback
  ---------------------------------------------------------------------- */
static void user_callback( s16 type )
{
    MBMCallback arg;
    
    if ( ! sUserCallbackFunc ) 
    {
        return;
    }
    
    if ( type > 0 )
    {
        arg.errcode = MBM_MEASURE_SUCCESS;
        arg.channel = type;
    }
    else
    {
        arg.errcode = type;
        arg.channel = 0;
    }
    sUserCallbackFunc( &arg );
}


