/*---------------------------------------------------------------------------*
  Project:  TwlSDK - tools - loadrun
  File:     loadrun.c

  Copyright 2005-2007 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.

  $Date:: 2008-05-23#$
  $Rev: 6187 $
  $Author: okubata_ryoma $
 *---------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <getopt.h>
#include <signal.h>
#include <time.h>

#include "ISNITRODLL.h"
#include "isd_api.h"

#define  SDK_BOOL_ALREADY_DEFINED_
#include <nitro_win32.h>
#include <nitro/os/common/system.h>

extern const unsigned long SDK_DATE_OF_LATEST_FILE;

//---- Version string
#define VERSION_STRING     " 1.13  Copyright 2005-2008 Nintendo. All right reserved."
// 1.13  Fixed print callback bug
// 1.12 Corrected help display
// 1.11 Deleted device type
// 1.10 Displayed latest time stamp of source file
// 1.9 Changed exit status of loadrun itself
// 1.8 Supported terminating character string from OS_EXIT()
// 1.7 Allowed data to be read from standard input
// 1.6 Disabled buffering of standard output
// 1.5 Allowed character string termination even in Quiet Mode
// 1.4 Implemented wrap display
// 1.3 Implemented execution timeout
// 1.2 Changed processing when execution is stopped using ctrl-c
// 1.1 Made it so the slot is turned off when execution is stopped using ctrl-c
// 1.0 Published

//---- exit number
#define EXIT_NUM_NO_ERROR               207     // Successful exit (however, this will not result next time)
#define EXIT_NUM_USER_SIGNAL            206     // Forced exit by user (ctrl-C)
#define EXIT_NUM_EXEC_TIME_OUT          205     // Forced exit due to execution timeout
#define EXIT_NUM_TIME_OUT               204     // Forced exit due to display timeout
#define EXIT_NUM_SHOW_DEVICES           203     // Exit on device list display
#define EXIT_NUM_SHOW_USAGE             202     // Exit on help display
#define EXIT_NUM_SHOW_VERSION           201     // Exit on version display
#define EXIT_NUM_STRING_ABORT           200     // Forced exit by character string

#define EXIT_NUM_NO_DEVICE              -1      // There are no available devices
#define EXIT_NUM_UNKNOWN_OPTION         -2      // An unknown option was specified
#define EXIT_NUM_ILLEGAL_OPTION         -3      // Illegal use of option
#define EXIT_NUM_NO_INPUT_FILE          -4      // The specified file does not exist or cannot be opened
#define EXIT_NUM_NOT_CONNECT            -5      // Failed to connect to device
#define EXIT_NUM_CANNOT_USE_CARTRIDGE   -6      // Failed to lock cartridge
#define EXIT_NUM_CANNOT_USE_CARD        -7      // Failed to lock card
#define EXIT_NUM_PRINTF_ERROR           -8      // Error while handling printf data
#define EXIT_NUM_LOADING_ERROR          -9      // Error during loading

//---- For device specification
char   *gDeviceName[] = {
    "CGBUSB", "CGBSCSI", "NITROUSB", "NITROUIC", NULL
};
int     gDeviceTypeArray[] = {
    ISNTD_DEVICE_CGB_EMULATOR_USB,
    ISNTD_DEVICE_CGB_EMULATOR_SCSI,
    ISNTD_DEVICE_IS_NITRO_EMULATOR,
    ISNTD_DEVICE_IS_NITRO_UIC
};

//---- Operating mode
BOOL    gQuietMode = FALSE;            // Quiet mode
BOOL    gVerboseMode = FALSE;          // verbose mode
BOOL    gDebugMode = FALSE;            // Debug mode

BOOL    gStdinMode = FALSE;            // stdin mode

BOOL    gIsTypeSpecified = FALSE;      // Is there a device type specification?
int     gSpecifiedType;                // Device type if one is specified

BOOL    gIsSerialSpecified = FALSE;    // Is there a serial number specification?
int     gSpecifiedSerial;              // Serial number if one was specified

BOOL    gIsCartridgeLocked = FALSE;    // Should the cartridge slot be locked?
BOOL    gIsCardLocked = FALSE;         // Should the card slot be locked?

int     gTimeOutTime = 0;              // Timeout interval in sec (0 indicates no timeout)
BOOL    gTimeOutOccured = FALSE;       // Has a timeout occurred?

int     gExecTimeOutTime = 0;          // Execution timeout interval in sec (0 indicates no timeout)
int     gExecTimeOutOccured = FALSE;   // Has an execution timeout occurred?

char   *gAbortString = NULL;           // Abort string
BOOL    gStringAborted = FALSE;        // Has execution terminated due to the abort string?

BOOL    gExitAborted = FALSE;          // Exit from OS_Exit()
int     gExitStatusNum = EXIT_NUM_STRING_ABORT; // Return value when terminated by OS_Exit()
int     gExitStrLength;                // Length of exit string

//---- For NITRO library
HINSTANCE gDllInstance;
NITRODEVICEHANDLE gDeviceHandle;
NITRODEVICEID gDeviceId;

//Connected to the device? (for slot switch)
BOOL    gDeviceConnected = FALSE;


//---- Device list
#define DEVICE_MAX_NUM      256
#define DEVICE_SERIAL_NONE  0x7fffffff // means no specified
ISNTDDevice gDeviceList[DEVICE_MAX_NUM];
int     gCurrentDevice = -1;
int     gConnectedDeviceNum = 0;

int     gDeviceTypeSpecified = ISNTD_DEVICE_NONE;
int     gDeviceSerialSpecified = DEVICE_SERIAL_NONE;    // means no specified

//---- Input file
#define FILE_NAME_MAX_SIZE  1024
FILE   *gInputFile;
char    gInputFileNameString[FILE_NAME_MAX_SIZE];
BOOL    gIsInputFileOpened = FALSE;

//---- Time
time_t  gStartTime = 0;                // Start time
BOOL    gIsLineHead = TRUE;            // Start of line?
BOOL    gShowLapTime = FALSE;

//---- Signal
BOOL    gStoppedByUser = FALSE;        // Indicates whether execution was stopped by the user.


#define printfIfNotQuiet(str)    do{if(!gQuietMode){fputs(str,stdout);}}while(0)

void    displayErrorAndExit(int exitNum, char *message);
BOOL    outputString(char *buf, int bufSize);

/*---------------------------------------------------------------------------*
  Name:         myExit

  Description:  similar to exit()

  Arguments:    exitNum: exit() number

  Returns:      None.
 *---------------------------------------------------------------------------*/
void myExit(int exitNum)
{
    //---- Turns off the slot for the cartridge and card
    if (gDeviceConnected)
    {
        (void)ISNTD_CartridgeSlotPower(gDeviceHandle, FALSE);
        (void)ISNTD_CardSlotPower(gDeviceHandle, FALSE);
    }

    //---- Deallocate DLL memory
    ISNTD_FreeDll();

    if (!gQuietMode)
    {
        if (exitNum == EXIT_NUM_USER_SIGNAL)
        {
            printf("\n*** loadrun: stopped by user.\n");
        }
        else if (exitNum == EXIT_NUM_TIME_OUT)
        {
            printf("\n*** loadrun: stopped by print timeout.\n");
        }
        else if (exitNum == EXIT_NUM_EXEC_TIME_OUT)
        {
            printf("\n*** loadrun: stopped by exec timeout.\n");
        }
        else if (gStringAborted)
        {
            printf("\n*** loadrun: stopped by aborting string.\n");
        }
        else if (gExitAborted)
        {
            exitNum = gExitStatusNum;
        }
    }

    exit(exitNum);
}

/*---------------------------------------------------------------------------*
  Name:         listDevice

  Description:  Displays a list of devices and terminates

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void listDevice(void)
{
    int     n;

    //---- Device read
    gConnectedDeviceNum = ISNTD_GetDeviceList(&gDeviceList[0], DEVICE_MAX_NUM);
    if (gConnectedDeviceNum < 0)
    {
        displayErrorAndExit(EXIT_NUM_NO_DEVICE, "Cannot access devices.");
    }

    printf("---- Connected devices:\n");

    for (n = 0; n < gConnectedDeviceNum; n++)
    {
        switch (gDeviceList[n].type)
        {
        case ISNTD_DEVICE_CGB_EMULATOR_USB:
            printf("%3d: [CGBUSB]  IS-CGB-EMU(USB) serial:%8d\n", n, gDeviceList[n].serial);
            break;
        case ISNTD_DEVICE_CGB_EMULATOR_SCSI:
            printf("%3d: [CGBSCSI]  IS-CGB-EMULATOR serial(host-id):%02d%02d\n", n,
                   gDeviceList[n].host, gDeviceList[n].serial);
            break;
        case ISNTD_DEVICE_IS_NITRO_EMULATOR:
            printf("%3d: [NITROUSB]  IS-NITRO-EMULATOR serial:%08d\n", n, gDeviceList[n].serial);
            break;
        case ISNTD_DEVICE_IS_NITRO_UIC:
            printf("%3d: [NITROUIC]  IS-NITRO-UIC serial: %08d\n", n, gDeviceList[n].serial);
            break;
        case ISNTD_DEVICE_UNKNOWN:
            printf("%3d: unknown device %x\n", n, (int)gDeviceList[n].ntdId);
            break;
        default:
            printf("Illegal device\n");
            break;
        }
    }

    //---- Search result
    printf("%d device(s) found.\n", gConnectedDeviceNum);

    myExit(EXIT_NUM_SHOW_DEVICES);
}

/*---------------------------------------------------------------------------*
  Name:         searchDevice

  Description:  search device

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void searchDevice(void)
{
    //---- If there is no device
    if (gConnectedDeviceNum <= 0)
    {
        displayErrorAndExit(EXIT_NUM_NO_DEVICE, "found no device.");
    }

    //---- If there was some kind of specification
    if (gDeviceTypeSpecified != ISNTD_DEVICE_NONE || gDeviceSerialSpecified != DEVICE_SERIAL_NONE)
    {
        int     n;
        gCurrentDevice = -1;
        for (n = 0; n < gConnectedDeviceNum; n++)
        {
            //---- Matching with specified device
            if (gDeviceTypeSpecified != ISNTD_DEVICE_NONE
                && gDeviceTypeSpecified != gDeviceList[n].type)
            {
                continue;
            }

            //---- Matching with specified serial
            if (gDeviceSerialSpecified != DEVICE_SERIAL_NONE
                && gDeviceSerialSpecified != gDeviceList[n].serial)
            {
                continue;
            }

            gCurrentDevice = n;
            break;
        }
    }
    //---- First one if there is no specification
    else
    {
        gCurrentDevice = 0;
    }

    //---- The specified device does not exist or is incorrect
    if (gCurrentDevice < 0
        || gDeviceList[gCurrentDevice].type == ISNTD_DEVICE_NONE
        || gDeviceList[gCurrentDevice].type == ISNTD_DEVICE_UNKNOWN)
    {
        displayErrorAndExit(EXIT_NUM_NO_DEVICE, "illegal device.");
    }
}

/*---------------------------------------------------------------------------*
  Name:         displayUsage

  Description:  Displays the usage

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void displayUsage(void)
{
    fprintf(stderr,
            "NITRO-SDK Development Tool - loadrun - Execute NITRO ROM image\n"
            "Build %lu\n\n"
            "Usage: loadrun [OPTION] <SrlFile>\n"
            "\t\tdownload Nitro srl file to debugger and execute.\n\n"
            "Options:\n"
            "  --version                   : Show version.\n"
            "  -h, --help                  : Show this help.\n"
            "  -q, --quiet                 : Quiet mode.\n"
            "  -v, --verbose               : Verbose mode.\n"
            "  -L, --list                  : List connecting device.\n"
            "  -l, --lap                   : Show lap time at each line.\n"
//            "  -d, --type=DEVICE          : Specify device type.\n"
//            "                                DEVICE=CGBUSB|CGBSCSI|NITROUSB|NITROUIC.\n"
            "  -s, --serial=SERIAL         : Specify serial number.\n"
            "  -t, --timeout=SECOND        : Specify quit time after last print.\n"
            "  -T, --exec-timeout=SECOND   : Specify quit time after execute program.\n"
            "  -a, --abort-string=STRING   : Specify aborting string.\n"
            "  -c, --card-slot=SWITCH      : Card      slot SWITCH=ON|OFF, default OFF.\n"
            "  -C, --cartridge-slot=SWITCH : Cartridge slot SWITCH=ON|OFF, default OFF.\n"
            "  --stdin, --standard-input   : Read data from stdin instead of <SrlFile>.\n\n",
            SDK_DATE_OF_LATEST_FILE);
}

/*---------------------------------------------------------------------------*
  Name:         displayVersion

  Description:  Displays the version

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void displayVersion(void)
{
    printf("*** loadrun: %s\n", VERSION_STRING);
}

/*---------------------------------------------------------------------------*
  Name:         displayErrorAndExit

  Description:  Displays errors.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void displayErrorAndExit(int exitNum, char *message)
{
    printf("*** loadrun: Error: %s\n", message);

    //---- Deallocate DLL memory
    ISNTD_FreeDll();

    exit(exitNum);
}

/*---------------------------------------------------------------------------*
  Name:         parseOption

  Description:  parses the option line

  Arguments:    argc : argument count
                argv: argument vector

  Returns:      result. less than 0 if error.
 *---------------------------------------------------------------------------*/
void parseOption(int argc, char *argv[])
{
    int     n;
    int     c;
    BOOL    helpFlag = FALSE;

    struct option optionInfo[] = {
        {"help", no_argument, NULL, 'h'},
        {"quiet", no_argument, NULL, 'q'},
        {"verbose", no_argument, NULL, 'v'},
        {"list", no_argument, NULL, 'L'},
        {"lap", no_argument, NULL, 'l'},
        {"debug", no_argument, NULL, 'D'},      //Hidden options
        {"version", no_argument, NULL, '1'},
        {"stdin", no_argument, NULL, 'I'},
        {"standard-input", no_argument, NULL, 'I'},
        {"type", required_argument, 0, 'd'},    //Hidden options
        {"serial", required_argument, 0, 's'},
        {"timeout", required_argument, 0, 't'},
        {"exec-timeout", required_argument, 0, 'T'},
        {"abort-string", required_argument, 0, 'a'},
        {"card-slot", required_argument, 0, 'c'},
        {"cartridge-slot", required_argument, 0, 'C'},
        {NULL, 0, 0, 0}
    };
    int     optionIndex;

    char   *optionStr = NULL;

    //---- suppress error string of getopt_long()
    opterr = 0;

    while (1)
    {
        c = getopt_long(argc, argv, "+hqvlLDd:s:t:T:a:c:C:", &optionInfo[0], &optionIndex);

        //printf("optind=%d optopt=%d  %x(%c) \n", optind, optopt, c,c );

        if (c == -1)
        {
            break;
        }

        switch (c)
        {
        case 'I':                     //---- Standard input
            gStdinMode = TRUE;
            break;
        case 'h':                     //---- Help display
            helpFlag = TRUE;
            break;
        case 'q':                     //---- Quiet mode
            gQuietMode = TRUE;
            break;
        case 'v':                     //---- Verbose mode
            gVerboseMode = TRUE;
            break;
        case 'D':                     //---- Debug mode
            gDebugMode = TRUE;
            break;
        case '1':                     //---- Version display
            displayVersion();
            myExit(EXIT_NUM_SHOW_VERSION);
            break;
        case 'L':                     //---- Device list
            listDevice();
            break;
        case 'l':                     //---- Lap time
            gShowLapTime = TRUE;
            break;
        case 'd':                     //---- Device
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            {
                int     n;
                for (n = 0; gDeviceName[n]; n++)
                {
                    if (!strcmp(optionStr, gDeviceName[n]))
                    {
                        gDeviceTypeSpecified = gDeviceTypeArray[n];
                        break;
                    }
                }

                if (gDeviceTypeSpecified == ISNTD_DEVICE_NONE)
                {
                    displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION, "illegal device type.");
                }
            }
            break;
        case 's':                     //---- Serial specification
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            gDeviceSerialSpecified = atoi(optionStr);
            break;
        case 'c':                     //---- Card slot lock
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            if (!strcmp(optionStr, "ON") || !strcmp(optionStr, "on"))
            {
                gIsCardLocked = TRUE;
            }
            else if (!strcmp(optionStr, "OFF") || !strcmp(optionStr, "off"))
            {
                gIsCardLocked = FALSE;
            }
            else
            {
                displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION, "illegal value for card slot option.");
            }
            break;
        case 'C':                     //---- Cartridge slot lock
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            if (!strcmp(optionStr, "ON") || !strcmp(optionStr, "on"))
            {
                gIsCartridgeLocked = TRUE;
            }
            else if (!strcmp(optionStr, "OFF") || !strcmp(optionStr, "off"))
            {
                gIsCartridgeLocked = FALSE;
            }
            else
            {
                displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION,
                                    "illegal value for cartridge slot option.");
            }
            break;
        case 't':                     //---- Timeout interval beginning from final display
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            gTimeOutTime = atoi(optionStr);
            if (gTimeOutTime <= 0)
            {
                displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION,
                                    "illegal value for abort timeout option.");
            }
            break;
        case 'T':                     //---- Execution timeout interval
            optionStr = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            gExecTimeOutTime = atoi(optionStr);
            if (gExecTimeOutTime <= 0)
            {
                displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION,
                                    "illegal value for abort exec timeout option.");
            }
            break;
        case 'a':                     //---- Abort string
            gAbortString = (char *)(optarg + ((*optarg == '=') ? 1 : 0));
            {
                int     length = strlen(gAbortString);
                if (length <= 0 || length > 256)
                {
                    displayErrorAndExit(EXIT_NUM_ILLEGAL_OPTION,
                                        "illegal value for abort string option.");
                }
            }
            break;
        default:
            displayErrorAndExit(EXIT_NUM_UNKNOWN_OPTION, "unknown option.");
        }
    }

    //---- Help display
    {
        BOOL    isDisplayHelp = FALSE;

        if (helpFlag)
        {
            isDisplayHelp = TRUE;
        }
        else if (argc <= optind && !gStdinMode)
        {
            isDisplayHelp = TRUE;
        }
        else if (argc > optind && gStdinMode)
        {
            isDisplayHelp = TRUE;
        }

        if (isDisplayHelp)
        {
            displayUsage();
            exit(EXIT_NUM_SHOW_USAGE);
        }
    }

    //---- Input file name
    if (!gStdinMode)
    {
        strncpy(gInputFileNameString, argv[optind], FILE_NAME_MAX_SIZE);
    }

    if (gVerboseMode)
    {
        if (!gStdinMode)
        {
            printf("Input file is [%s]\n", gInputFileNameString);
        }
        else
        {
            printf("Input file is stdin\n");
        }
    }

    //---- Information display
    if (gVerboseMode)
    {
        printf("Print time out : %d sec.\n", gTimeOutTime);
        printf("Execute time out : %d sec.\n", gExecTimeOutTime);
        printf("Card lock : %s.\n", (gIsCardLocked) ? "ON" : "OFF");
        printf("Cartridge lock : %s.\n", (gIsCartridgeLocked) ? "ON" : "OFF");

        if (gAbortString)
        {
            printf("Abort string : [%s]\n", gAbortString);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         loadFile

  Description:  loads file

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void loadFile(void)
{
    unsigned int address = 0;
    //int fileSize;

    //---- File open
    if (gStdinMode)
    {
        gInputFile = stdin;
        _setmode(_fileno(gInputFile), O_BINARY);
    }
    else
    {
        if ((gInputFile = fopen(gInputFileNameString, "rb")) == NULL)
        {
            displayErrorAndExit(EXIT_NUM_NO_INPUT_FILE, "cannot open input file.");
        }
    }
    gIsInputFileOpened = TRUE;

    //---- Connected to device
    if ((gDeviceHandle = ISNTD_DeviceOpen(gDeviceList[gCurrentDevice].ntdId)) == NULL)
    {
        displayErrorAndExit(EXIT_NUM_NOT_CONNECT, "cannot connect device.");
    }
    gDeviceConnected = TRUE;

    //---- Issue reset
    ISNTD_Reset(gDeviceHandle, TRUE);
    Sleep(1000);

    //---- Get file size
    //fseek( gInputFile, 0L, SEEK_END );
    //fileSize = ftell( gInputFile );
    //fseek( gInputFile, 0L, SEEK_SET );

    //---- Transfer every 16KB
    while (1)
    {
        char    buf[16384];
        size_t  size = fread(buf, 1, sizeof(buf), gInputFile);
        static int progressCount = 0;

        if (!size)
        {
            break;
        }

        //---- Transfer
        if (!ISNTD_WriteROM(gDeviceHandle, buf, address, size))
        {
            displayErrorAndExit(EXIT_NUM_LOADING_ERROR, "troubled while loading input file.");
        }

        address += size;

        if (gVerboseMode)
        {
            if (!(progressCount++ % 32))
            {
                printf("*");
            }
        }
    }

    //---- File close
    if (gStdinMode)
    {
        _setmode(_fileno(gInputFile), O_TEXT);
    }
    else
    {
        fclose(gInputFile);
    }
    gIsInputFileOpened = FALSE;

    if (gVerboseMode)
    {
        printf("\nInput file size: %d (0x%x) byte\n", address, address);
    }
}

/*---------------------------------------------------------------------------*
  Name:         setSlopPower

  Description:  Slot power process

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void setSlotPower(void)
{
    //---- Should the cartridge slot be locked?
    if (gIsCartridgeLocked)
    {
        if (!ISNTD_CartridgeSlotPower(gDeviceHandle, TRUE))
        {
            displayErrorAndExit(EXIT_NUM_CANNOT_USE_CARTRIDGE, "cannot use cartridge slot.");
        }
    }

    //---- Should the card slot be locked?
    if (gIsCardLocked)
    {
        if (!ISNTD_CardSlotPower(gDeviceHandle, TRUE))
        {
            displayErrorAndExit(EXIT_NUM_CANNOT_USE_CARD, "cannot use card slot.");
        }
    }

    //---- Cancel reset
    Sleep(1000);
    ISNTD_Reset(gDeviceHandle, FALSE);
}

/*---------------------------------------------------------------------------*
  Name:         procPrint

  Description:  printf process

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
NITROArch archNum[] = {
    NITROArchARM9,
    NITROArchARM7
};

#define PRINT_ONETIME_SIZE  512
//#define PRINT_ONETIME_SIZE    16

//---- Buffer for joint display
static char gConbineBuf[PRINT_ONETIME_SIZE * 2 + 2] = "\0";
static char *gConbineBufPtr = &gConbineBuf[0];

//---- Buffer for joining and comparing character strings
static char gLineBuf[PRINT_ONETIME_SIZE + 1];


void procPrintf(void)
{
    int     blankTime = 0;

    //---- Size of exit string
    gExitStrLength = strlen(OS_EXIT_STRING_1);

    while (1)
    {
        BOOL    isOutputString = FALSE;

        //---- When stopped by the user
        if (gStoppedByUser)
        {
            myExit(EXIT_NUM_USER_SIGNAL);
        }

        //printf("[######]\n" );
        {
            int     n;
            int     dataSize;
            char    tmpBuf[PRINT_ONETIME_SIZE + 1];

            for (n = 0; n < 2; n++)
            {
                //---- Get display data
                if (!ISNTD_GetDebugPrint
                    (gDeviceHandle, archNum[n], tmpBuf, &dataSize, PRINT_ONETIME_SIZE))
                {
                    ISNTD_DeviceClose(gDeviceHandle);
                    displayErrorAndExit(EXIT_NUM_PRINTF_ERROR,
                                        "troubled while receiving print data.");
                }
                tmpBuf[dataSize] = '\0';

                //---- If there is data to be displayed, display it
                if (dataSize)
                {
                    //---- Record that data was displayed
                    isOutputString = TRUE;

                    //---- Output
                    if (!outputString(tmpBuf, dataSize))
                    {
                        break;
                    }
                }
            }
        }

        //---- Exit?
        if (gStringAborted || gExitAborted)
        {
            break;
        }

        //---- If text is not being displayed
        if (!isOutputString)
        {
            Sleep(100);
            blankTime += 100;

            //---- Timeout decision
            if (gTimeOutTime && blankTime > gTimeOutTime * 1000)
            {
                gTimeOutOccured = TRUE;
                break;
            }
        }
        //---- If text has been displayed
        else
        {
            blankTime = 0;
        }

        //---- Timeout check
        if (gExecTimeOutTime > 0)
        {
            time_t  currentTime;
            (void)time(&currentTime);

            if (currentTime - gStartTime >= gExecTimeOutTime)
            {
                gExecTimeOutOccured = TRUE;
                break;
            }
        }
    }
}


/*---------------------------------------------------------------------------*
  Name:         showLapTime

  Description:  displays lap time at line head

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void showLapTime(void)
{
    int     lap;
    time_t  currentTime;

    (void)time(&currentTime);
    lap = currentTime - gStartTime;

    printf("{%d:%02d}", lap / 60, lap % 60);
}

/*---------------------------------------------------------------------------*
  Name:         outputString

  Description:  outputs string to stdout

  Arguments:    buf: buffer
                bufSize: data size in buffer

  Returns:      FALSE if to do quit.
 *---------------------------------------------------------------------------*/
BOOL outputString(char *buf, int bufSize)
{
    char   *bufEnd = buf + bufSize;
    char   *p = buf;

    int     abortStrLength = gAbortString ? strlen(gAbortString) : 0;

    while (p < bufEnd)
    {
        char   *crPtr = strchr(p, '\n');

        //---- \n missing
        if (!crPtr)
        {
            //---- Save for comparison
            strcat(gConbineBufPtr, p);
            gConbineBufPtr += strlen(p);

            //---- Time display
            if (!gQuietMode && gIsLineHead && gShowLapTime)
            {
                showLapTime();
            }
            gIsLineHead = FALSE;

            //---- Display
            printfIfNotQuiet(p);

            //---- Destroy if buffer overflows (the responsibility is on the output side to insert \n)
            if (gConbineBufPtr - &gConbineBuf[0] > PRINT_ONETIME_SIZE)
            {
                gConbineBufPtr = &gConbineBuf[0];
                *gConbineBufPtr = '\0';
            }

            break;
        }

        //---- copy up to \n
        {
            int     n = crPtr - p + 1;

            //---- Combine for comparison
            strncpy(gConbineBufPtr, p, n);
            gConbineBufPtr[n] = '\0';

            //---- For display
            strncpy(&gLineBuf[0], p, n);
            gLineBuf[n] = '\0';
        }

        //---- Time display
        if (!gQuietMode && gIsLineHead && gShowLapTime)
        {
            showLapTime();
        }
        gIsLineHead = TRUE;

        //---- Line display
        printfIfNotQuiet(gLineBuf);

        //---- Compare with abort string
        if (gAbortString && !strncmp(gConbineBuf, gAbortString, abortStrLength))
        {
            gStringAborted = TRUE;
            return FALSE;
        }

        //---- Exit using OS_Exit
        if (!strncmp(gConbineBuf, OS_EXIT_STRING_1, gExitStrLength))
        {
            gExitAborted = TRUE;
            gExitStatusNum = atoi(gConbineBuf + gExitStrLength);
            return FALSE;
        }

        gConbineBufPtr = &gConbineBuf[0];
        *gConbineBufPtr = '\0';

        p = crPtr + 1;
    }

    return TRUE;
}

/*---------------------------------------------------------------------------*
  Name:         signalHandler

  Description:  signal handler

  Arguments:    sig
                argv: argument vector

  Returns:      ---
 *---------------------------------------------------------------------------*/
void signalHandler(int sig)
{
    gStoppedByUser = TRUE;
}

/*---------------------------------------------------------------------------*
  Name:         Main

  Description:  main proc

  Arguments:    argc : argument count
                argv: argument vector

  Returns:      ---
 *---------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
    //---- Initialize DLL
    ISNTD_InitDll();

    //---- Disable buffering of standard output
    setvbuf(stdout, NULL, _IONBF, 0);

    //---- Option parsing
    parseOption(argc, argv);

    //---- Device read
    gConnectedDeviceNum = ISNTD_GetDeviceList(&gDeviceList[0], DEVICE_MAX_NUM);
    if (gConnectedDeviceNum < 0)
    {
        displayErrorAndExit(EXIT_NUM_NO_DEVICE, "Cannot access devices.");
    }

    //---- Search for device
    searchDevice();

    //---- Read
    loadFile();

    //---- Signal setting
    (void)signal(SIGINT, signalHandler);

    //---- Slot
    setSlotPower();

    //---- Get start time
    (void)time(&gStartTime);

    //---- printf process
    procPrintf();

    //---- End
    if (gExitAborted)                  //---- Exit from OS_Exit()
    {
        myExit(gExitStatusNum);
    }
    else if (gStringAborted)           //---- Abort string
    {
        myExit(EXIT_NUM_STRING_ABORT);
    }
    else if (gTimeOutOccured)          //---- Timeout
    {
        myExit(EXIT_NUM_TIME_OUT);
    }
    else if (gExecTimeOutOccured)
    {
        myExit(EXIT_NUM_EXEC_TIME_OUT);
    }
    else                               //---- Normal end
    {
        myExit(EXIT_NUM_NO_ERROR);
    }
    //---- never reached here

    //---- dummy to avoid warning
    return 0;
}
