/*---------------------------------------------------------------------------*
  Project:  NitroSDK - WBT - demos - wbt-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.7  2004/11/04 04:00:27  yosizaki
  fix comment.

  Revision 1.6  2004/10/31 23:53:18  yosizaki
  delete WC_DEFAULT_CHANNEL (abandoned symbol)

  Revision 1.5  2004/10/28 11:05:36  yosizaki
  change to use block-transfer callback of wc.
  fix around sub-BG VRAM mappings.

  Revision 1.4  2004/10/19 10:27:02  miya
  Changed so that when a Port receive callback is used print characters are displayed on the NITRO screen.

  Revision 1.3  2004/10/04 08:40:31  miya
  add WBT_CancelCurrentCommand

  Revision 1.2  2004/10/04 05:51:10  miya
  change transfer size according to the programing guideline

  Revision 1.1  2004/08/23 09:47:33  yosizaki
  (none)

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

/*---------------------------------------------------------------------------*
    This is a simple sample that performs wireless communications using the WM library.
    It automatically connects to a neighboring simple demo, and uses wireless communications to transfer a 4-byte frame count

    HOWTO:
        1. To start communications as a parent press the A button.
           As soon as it finds a nearby child that has the same simple demo, it automatically
           begins communicating with the child. It can simultaneously communicate with up to 15 children.
        2. To start communications as a child press the B button.
           As soon as it finds a nearby parent that has the same simple demo, it automatically
           begins communicating with the parent.
        3. You can reset the connection and return to the initial screen by pressing the START button while either of these screens are displayed: busy screen; the communications screen that appears after the connection has completed.

 *---------------------------------------------------------------------------*/

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

#include    "font.h"
#include    "text.h"
#include    "wc.h"
#include    "bt.h"


#define MIYA_PRINT 1


/*---------------------------------------------------------------------------*
    Constant Definitions
 *---------------------------------------------------------------------------*/
#define     KEY_REPEAT_START    25      // Number of frames until key repeat starts
#define     KEY_REPEAT_SPAN     10      // Key repeat span (number of frames)

// Game information
static const WMpparam   wcDefaultParameter =
{
    NULL ,  0 , 0 ,
    GGID_WBT_1 ,                // Game group ID
    0x0000 ,                    // temporary ID (incremented by 1 each time)
    1 ,                         // Entry permission flag
    15 ,                        // Maximum number of connected children
    0 ,                         // Multiboot flag
    0 ,                         // Key sharing
    1 ,                         // Continuous send flag
    200 ,                       // beacon interval
    {   0 , 0 , 0 , 0   } ,     // User name
    {   0 , 0 , 0 , 0 , 0 , 0 , 0 , 0   } , // Game name
    1 ,                         // Connect channel (Does MeasureChannel at start time)
    WC_PARENT_DATA_SIZE_MAX,     // Parent send data size
    WC_CHILD_DATA_SIZE_MAX      // Child send data size
    // Reserves
};

/*---------------------------------------------------------------------------*
    Structure Definitions
 *---------------------------------------------------------------------------*/
// Key input information
typedef struct KeyInfo
{
    u16     cnt;    // Raw input value
    u16     trg;    // Trigger press input
    u16     up;     // Trigger release input
    u16     rep;    // Press / hold 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

// Function called when data is received
static void    ParentReceiveCallback( u16 aid , u16* data , u16 length );
static void    ChildReceiveCallback( u16 aid , u16* data , u16 length );

// Function called when data is sent
void    ParentSendCallback( void );
void    ChildSendCallback( void );

// Block transfer status notification function
static void     BlockTransferCallback(WMCallback *arg);

// General purpose sub routine
static void     KeyRead( KeyInfo* pKey );
static void     ClearString( int vram_num );
static void     PrintString( s16 x , s16 y , u8 palette , char* text , ... );
static void     InitializeAllocateSystem( void );


/*---------------------------------------------------------------------------*
    Internal variable definitions
 *---------------------------------------------------------------------------*/
static KeyInfo  gKey;                   // Key input
static s32      gFrame;                 // Frame counter

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

static int send_counter[16];
static int recv_counter[16];


#define PRINTF mprintf

#define VRAM_SIZE 2*32*32
static u8 g_screen[NUM_OF_SCREEN][VRAM_SIZE]  ATTRIBUTE_ALIGN(32);


static TEXT_CTRL textctrl[NUM_OF_SCREEN];
TEXT_CTRL *tc[NUM_OF_SCREEN];

static int vram_num[2];
static int screen_toggle = 0;

static u32 v_blank_intr_counter = 0;
#define TEXT_HEAPBUF_SIZE 0x8000
static u8 text_heap_buffer[TEXT_HEAPBUF_SIZE];


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

  Description:  Initialization and main loop

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
NitroMain( void )
{
    int i;
    // Each type of initialization
    OS_Init();
    OS_InitTick();

    // Allocate Memory
    (void)init_text_buf_sys((void *)&(text_heap_buffer[0]),
			    (void *)&(text_heap_buffer[TEXT_HEAPBUF_SIZE]));

    for( i = 0 ; i < NUM_OF_SCREEN ; i++) {
      tc[i] = &(textctrl[i]);
      init_text(tc[i],  (u16 *)&(g_screen[i]),  4 /* pal no. */ );
    }

    FX_Init();
    GX_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 );


  /* BG0 display settings */
  GX_SetBankForBG(GX_VRAM_BG_128_A);
  G2_SetBG0Control(GX_BG_SCRSIZE_TEXT_256x256,
		   GX_BG_COLORMODE_16,
		   GX_BG_SCRBASE_0xf000,	// SCR base block 31
		   GX_BG_CHARBASE_0x00000,	// CHR base block 0
		   GX_BG_EXTPLTT_01);
  G2_SetBG0Priority(0);
  G2_BG0Mosaic(FALSE);
  // 2D display settings
  GX_SetGraphicsMode(GX_DISPMODE_GRAPHICS,GX_BGMODE_0,GX_BG0_AS_2D);
  GX_LoadBG0Char(d_CharData,0,sizeof(d_CharData));
  GX_LoadBGPltt(d_PaletteData,0,sizeof(d_PaletteData));

  // OBJ display settings
  GX_SetBankForOBJ(GX_VRAM_OBJ_128_B);
  GX_SetOBJVRamModeChar(GX_OBJVRAMMODE_CHAR_2D);

  GX_SetVisiblePlane(GX_PLANEMASK_BG0 | GX_PLANEMASK_OBJ );

  /* Sub BG0 display settings */
  GX_SetBankForSubBG( GX_VRAM_SUB_BG_48_HI );
  G2S_SetBG0Control(
		    GX_BG_SCRSIZE_TEXT_256x256 ,
		    GX_BG_COLORMODE_16 ,
		    GX_BG_SCRBASE_0xb800 ,		// SCR base block 23
		    GX_BG_CHARBASE_0x00000 ,	// CHR base block 0
		    GX_BG_EXTPLTT_01
		    );
  G2S_SetBG0Priority( 0 );
  G2S_BG0Mosaic( FALSE );
  GXS_SetGraphicsMode(GX_BGMODE_0);
  GXS_LoadBG0Char(d_CharData,0,sizeof(d_CharData));
  GXS_LoadBGPltt(d_PaletteData,0,sizeof(d_PaletteData));

  GX_SetBankForSubOBJ(GX_VRAM_SUB_OBJ_128_D);
  GXS_SetOBJVRamModeChar(GX_OBJVRAMMODE_CHAR_2D);

  GXS_SetVisiblePlane( GX_PLANEMASK_BG0 | GX_PLANEMASK_OBJ );

    G2_SetBG0Offset( 0, 0 );
    G2S_SetBG0Offset( 0, 0 );


    InitializeAllocateSystem();

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

  // for V Blank
  (void)OS_SetIrqFunction( OS_IE_V_BLANK, VBlankIntr );
  (void)OS_EnableIrqMask( OS_IE_V_BLANK );
  (void)OS_EnableIrq();
  (void)OS_EnableInterrupts();
  (void)GX_VBlankIntr( TRUE );


    //********************************
    // Initialize wireless
    WcInit(&wcDefaultParameter, FALSE, TRUE, 1);
	WcSetBlockTransferCallback(BlockTransferCallback);
    //********************************

    // Output debug character string
    PRINTF("ARM9: WBT-1 demo started.\n");
    vram_num[0] = 1;
    vram_num[1] = 0;


    // Main Loop

    for( gFrame = 0 ; TRUE ; gFrame ++ )
    {
      text_buf_to_vram(tc[vram_num[0]]);
      text_buf_to_vram(tc[vram_num[1]]);

      if ( gKey.trg & PAD_BUTTON_SELECT ) {
	screen_toggle ^= 1;
      }
      if ( gKey.trg & PAD_BUTTON_L ) {
	vram_num[screen_toggle]--;
	if( vram_num[screen_toggle] < 0 ) {
	  vram_num[screen_toggle] = (NUM_OF_SCREEN-1);
	}
      } else if ( gKey.trg & PAD_BUTTON_R ) {
	vram_num[screen_toggle]++;
	if( vram_num[screen_toggle] > (NUM_OF_SCREEN-1) ) {
	  vram_num[screen_toggle] = 0;
	}
      }

      // Acquire Key input information
      KeyRead( &gKey );

      // Assign process according to 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();
    }
}

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

  Description:  Processes on the 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 )
    {
        //********************************
        WBT_InitParent(  BT_PARENT_PACKET_SIZE , BT_CHILD_PACKET_SIZE , bt_callback );
        WcSetMpReceiveCallback(ParentReceiveCallback);
        bt_register_blocks();
        WcStartParent();
        //********************************
    }
    else if( gKey.trg & PAD_BUTTON_B )
    {
        //********************************
        WBT_InitChild( bt_callback);
        WcSetMpReceiveCallback(ChildReceiveCallback);
        WcStartChild();
        //********************************
	vram_num[1] = 2;
    }
}

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

  Description:  Processes on the 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:  Processes on the 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:  Processes on the parent communication screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ModeParent( void )
{
    PrintString( 8 , 1 , 0x2 , "Parent mode" );
    PrintString( 4 , 3 , 0x4 , "Send:     %08X" , send_counter[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 , recv_counter[i] );
            }
            else
            {
                PrintString( 5 , (s16)( 6 + i ) , 0x7 , "No child" );
            }
        }
    }

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

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

  Description:  Processes on the child communications screen

  Arguments:    None.

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

    if( gKey.trg & PAD_BUTTON_START )
    {
        //********************************
        WcEnd();
        //********************************
    }
    else if( gKey.trg & PAD_BUTTON_Y )
      {
	bt_start();
      }
    else if( gKey.trg & PAD_BUTTON_X )
      {
	bt_stop();
      }
}

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

  Description:  V-blank interrupt vector

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
VBlankIntr( void )
{
  vu16 *ptr;
  int i;

  DC_FlushRange( (void *)&(g_screen[vram_num[0]]),VRAM_SIZE);
  GX_LoadBG0Scr( &(g_screen[vram_num[0]]), 0 , VRAM_SIZE);

  DC_FlushRange( (void *)&(g_screen[vram_num[1]]),VRAM_SIZE);
  GXS_LoadBG0Scr( &(g_screen[vram_num[1]]) , 0 , VRAM_SIZE );

  if( screen_toggle )
    ptr = (u16 *)G2S_GetBG0ScrPtr();
  else
    ptr = (u16 *)G2_GetBG0ScrPtr();

  for( i = 0 ; i < 32 ; i++ ) {
    *ptr = (u16)((2 << 12) | (0x00ff & '='));
    ptr++;
  }


  v_blank_intr_counter++;
  OS_SetIrqCheckFlag( OS_IE_V_BLANK );
}

/*---------------------------------------------------------------------------*
  Name:         ParentReceiveCallback

  Description:  Function that is called when a parent receives data from a child.

  Arguments:    aid     - aid of send source child
                data    - Pointer to received data (NULL for disconnect notification)
                length  - Size of received data

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ParentReceiveCallback( u16 aid , u16* data , u16 length )
{
    if( data != NULL )
    {
        gRecvFlag[ aid ] = TRUE;
        // Copy source is 2 byte aligned (not 4-byte aligned)
        recv_counter[aid]++;
        WBT_MpParentRecvHook((u8 *)data,length, aid  );
    }
    else
    {
        gRecvFlag[ aid ] = FALSE;
    }
}


/*---------------------------------------------------------------------------*
  Name:         ChildReceiveCallback

  Description:  Function that is called when a child receives data from a parent.

  Arguments:    aid     - aid of sending source parent (always 0)
                data    - Pointer to received data (NULL for disconnect notification)
                length  - Size of received data

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ChildReceiveCallback( u16 aid , u16* data , u16 length )
{
    (void)aid;
    recv_counter[0]++;
    if( data != NULL )
    {
        gRecvFlag[ 0 ] = TRUE;
        // Copy source is 2 byte aligned (not 4-byte aligned)
        WBT_MpChildRecvHook((u8 *)data, length);
    }
    else
    {
        gRecvFlag[ 0 ] = FALSE;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ChildReceiveCallback

  Description:  Block transfer status notification function

  Arguments:    arg     - Callback pointer for the notification source WM function.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void     BlockTransferCallback(WMCallback *arg)
{
    WCStatus  wcStatus = WcGetStatus();
    switch(arg->apiid) {
    case WM_APIID_START_MP:
        {   /* Start MP state */
            WMStartMPCallback *cb = (WMStartMPCallback*)arg;
            switch(cb->state) {
            case WM_STATECODE_MP_START:
                if(wcStatus == WC_STATUS_PARENT) {
                    ParentSendCallback();
                }
                else if(wcStatus == WC_STATUS_CHILD) {
                    WBT_SetOwnAid(WcGetAid());
                    mfprintf(tc[2],"aid = %d\n",WcGetAid());
                    bt_start();
                    ChildSendCallback();
                }
                break;
            }
        }
        break;
    case WM_APIID_SET_MP_DATA:
        {   /* Complete single MP communication*/
            if( wcStatus == WC_STATUS_PARENT )
            {
                ParentSendCallback();
            }
            else if( wcStatus == WC_STATUS_CHILD )
            {
                ChildSendCallback();
            }
        }
        break;
    case WM_APIID_START_PARENT:
        {   /* New child connect */
            WMStartParentCallback *cb = (WMStartParentCallback*)arg;
            if( wcStatus == WC_STATUS_PARENT ) {
                switch(cb->state) {
                case WM_STATECODE_CONNECTED:
                    /*
                     * Begin communication from the first child to arrive.
                     * If already running will be ignored internally.
                     */
                    ParentSendCallback();
                    break;
                }
            }
        }
        break;
    }
}


/*---------------------------------------------------------------------------*
  Name:         ParentSendCallback

  Description:  Function that is called when a parent sends data to a child.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ParentSendCallback( void )
{
  const u16 size = (u16)WBT_MpParentSendHook(gSendBuf, WC_PARENT_DATA_SIZE_MAX);
  send_counter[0]++;
  WcSetParentData( gSendBuf , size );
}


/*---------------------------------------------------------------------------*
  Name:         ChildSendCallback

  Description:  Function that is called when a child receives data from a parent.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ChildSendCallback( void )
{
  const u16 size = (u16)WBT_MpChildSendHook(gSendBuf, WC_CHILD_DATA_SIZE_MAX);
  send_counter[0]++;
  WcSetChildData( gSendBuf , size );
}

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

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

  Arguments:    pKey  - Key input information structure 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 );       // Press and hold 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;      // Raw key input
}

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

  Description:  Clears the virtual screen

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ClearString(int vram_num)
{
   MI_DmaClear32( 0, (void*)&(g_screen[vram_num]) , VRAM_SIZE );
}


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

  Description:  Positions a character string on the virtual screen. The character string is maximum 32 characters.

  Arguments:    x       - x coordinate that positions the start point for the character string (X 8 dots)
                y       - y coordinate that positions the start point for the character string (X 8 dots)
                palette - Uses palette number to specify character color
                text    - The character string to be positioned Terminating character = NULL.
                ...     - 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)OS_VSNPrintf( temp ,32, text , vlist );
    va_end( vlist );
    *(u16*)( &temp[ 31 ] ) = 0x0000;
    for( i = 0 ; ; i ++ )
    {
        if( temp[ i ] == 0x00 )
        {
            break;
        }
	tc[1]->screen[(y * 32 ) + x + i] = (u16)((palette << 12) | temp[i]);
    }
}


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

  Description:  Uses arena in main memory to initialize memory allocation system.

  Arguments:    None.

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

    mprintf(" arena lo = %08x\n",OS_GetMainArenaLo());
    mprintf(" arena hi = %08x\n",OS_GetMainArenaHi());

    // Assume that OS_Init has been 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
 *---------------------------------------------------------------------------*/
