/*
 * main.c
 * BuGS
 *
 * Created by Jeremy Rand on 2020-06-10.
 * Copyright (c) 2020 Jeremy Rand. All rights reserved.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <orca.h>
#include <Memory.h>
#include <Locator.h>
#include <MiscTool.h>

#include "main.h"
#include "game.h"
#include "netScores.h"
#include "settings.h"
#include "tileData.h"


/* Defines and macros */

#define GLOBAL_SCORE_REFRESH_TIME (15 * 60 * 60)
#define SHUTDOWN_NETWORK_TIMEOUT (2 * 60)
#define TCP_CONNECT_TIMEOUT (8 * 60)
#define READ_NETWORK_TIMEOUT (5 * 60)
#define NETWORK_RETRY_TIMEOUT (3 * 60 * 60)

#define TOOLFAIL(string) \
    if (toolerror()) SysFailMgr(toolerror(), (Pointer)"\p" string "\n\r    Error Code -> $");


/* Globals */

unsigned int myUserId;
unsigned int randomSeed;

// This symbol is used also from assembly directly so be careful with it...
char globalScoreInfo[GAME_NUM_TILES_WIDE + 1];

tNSGSHighScores globalHighScores;
Boolean hasGlobalHighScores = FALSE;
Word globalScoreAge = 0;
unsigned int scoreIndex = 0;


/* Implementation */


word randomMushroomOffset(void)
{
    /* We do not put mushrooms in the bottom tile so we subtract the width here to find
        a tile number above that last line */
    return (rand() % (NUM_GAME_TILES - GAME_NUM_TILES_WIDE)) * SIZEOF_TILE_INFO;
}


static void uploadSpin(int val)
{
    switch (val)
    {
        case 0:
            uploadSpin1();
            break;
            
        case 1:
            uploadSpin2();
            break;
            
        case 2:
            uploadSpin3();
            break;
            
        case 3:
            uploadSpin2();
            break;
    }
}


static void scorePosition(unsigned int position, unsigned int numberOfScores)
{
    int i;
    
    sprintf(globalScoreInfo, "  %u OF %u SCORES", position, numberOfScores);
    for (i = (int)strlen(globalScoreInfo); i < sizeof(globalScoreInfo); i++) {
        globalScoreInfo[i] = ' ';
    }
    globalScoreInfo[GAME_NUM_TILES_WIDE] = '\0';
    displayScorePosition();
    
    for (i = 6 * 60; i > 0; i--) {
        waitForVbl();
    }
    
    globalScoreAge = 0;
}


static void showConnectionString(BOOLEAN display)
{
    if (display)
        displayConnectionString();
}


static char hexDigitToAscii(Word digit)
{
    digit &= 0xf;
    if (digit < 10)
        return '0' + digit;
    
    if (digit > 15)
        return 'X';
    
    return 'A' + digit - 10;
}


static void displayString(Word row, char * string)
{
    strcpy(&(globalHighScores.score[row].scoreText[2]), string);
}

static void displayNetworkError(char * line1, char * line2, unsigned int errorCode)
{
    Word row;
    Word column;
    
    for (row = 0; row < sizeof(globalHighScores.score) / sizeof(globalHighScores.score[0]); row++) {
        for (column = 0;
             column < sizeof(globalHighScores.score[0].scoreText) / sizeof(globalHighScores.score[0].scoreText[0]);
             column++) {
            globalHighScores.score[row].scoreText[column] = ' ';
        }
        for (column = 0;
             column < sizeof(globalHighScores.score[0].who) / sizeof(globalHighScores.score[0].who[0]);
             column++) {
            globalHighScores.score[row].who[column] = ' ';
        }
    }
    
    displayString(1, "NETWORK");
    displayString(2, "PROBLEM");
    
    displayString(4, line1);
    displayString(5, line2);
    
    globalHighScores.score[7].scoreText[0] = 'C';
    globalHighScores.score[7].scoreText[1] = 'O';
    globalHighScores.score[7].scoreText[2] = 'D';
    globalHighScores.score[7].scoreText[3] = 'E';
    globalHighScores.score[7].scoreText[4] = ':';
    globalHighScores.score[7].scoreText[5] = ' ';
    globalHighScores.score[7].scoreText[6] = hexDigitToAscii(errorCode >> 12);
    globalHighScores.score[7].scoreText[7] = hexDigitToAscii(errorCode >> 8);
    globalHighScores.score[7].scoreText[8] = hexDigitToAscii(errorCode >> 4);
    globalHighScores.score[7].scoreText[9] = hexDigitToAscii(errorCode);
    
    hasGlobalHighScores = TRUE;
    globalScoreAge = 0;
}

static void displayError(tNSGSErrorType errorType, unsigned int errorCode)
{
    switch (errorType) {
        case NSGS_CONNECT_ERROR:
            displayNetworkError("CONNECT", "FAILED", errorCode);
            break;

        case NSGS_LOOKUP_ERROR:
            displayNetworkError("LOOKUP", "FAILED", errorCode);
            break;
            
        case NSGS_SOCKET_ERROR:
            displayNetworkError("SOCKET", "ERROR", errorCode);
            break;
            
        case NSGS_PROTOCOL_ERROR:
            displayNetworkError("PROTOCOL", "FAILED", errorCode);
            break;
    }
}


static void setHighScores(const tNSGSHighScores * highScores)
{
    memcpy(&globalHighScores, highScores, sizeof(globalHighScores));
    hasGlobalHighScores = TRUE;
    globalScoreAge = GLOBAL_SCORE_REFRESH_TIME;
}


static BOOLEAN shouldRefreshHighScores(void)
{
    return globalScoreAge == 0;
}


BOOLEAN sendHighScore(void)
{
    const tNSGSHighScore * highScore = getHighScore(scoreIndex / sizeof(tNSGSHighScore));
    return NSGS_SendHighScore(highScore->who, highScore->score);
}


int main(void)
{
    static tNSGSHighScoreInitParams highScoreInitParams;
    int event;
    Ref toolStartupRef;
    
    myUserId = MMStartUp();
    TOOLFAIL("Unable to start memory manager");
    
    TLStartUp();
    TOOLFAIL("Unable to start tool locator");
    
    toolStartupRef = StartUpTools(myUserId, refIsResource, TOOL_STARTUP);
    TOOLFAIL("Unable to start tools");
    
    CompactMem();
    /* Allocate $1000 extra before the SHR screen so I can write sprites above the start of the
       screen without overwriting memory I do not own. */
    NewHandle(0x9000, myUserId, attrLocked | attrFixed | attrAddr | attrBank, (Pointer)0x011000);
    TOOLFAIL("Unable to allocate SHR screen");
    
    randomSeed = (int)time(NULL);
    if (randomSeed == 0)
        randomSeed = 1;
    srand(randomSeed);
    randInit();
    
    InitMouse(0);
    SetMouse(transparent);
    
    highScoreInitParams.userId = myUserId;
    highScoreInitParams.scoreServer = (const char *)"\p" NETWORK_SERVER;
    highScoreInitParams.scorePort = NETWORK_SERVERPORT;
    highScoreInitParams.secret1 = NETWORK_SERVERSECRET1;
    highScoreInitParams.secret2 = NETWORK_SERVERSECRET2;
    highScoreInitParams.shutdownTimeout = SHUTDOWN_NETWORK_TIMEOUT;
    highScoreInitParams.connectTimeout = TCP_CONNECT_TIMEOUT;
    highScoreInitParams.readTimeout = READ_NETWORK_TIMEOUT;
    highScoreInitParams.retryTimeout = NETWORK_RETRY_TIMEOUT;
    
    highScoreInitParams.displayConnectionString = showConnectionString;
    highScoreInitParams.waitForVbl = waitForVbl;
    highScoreInitParams.uploadSpin = uploadSpin;
    highScoreInitParams.scorePosition = scorePosition;
    highScoreInitParams.displayError = displayError;
    highScoreInitParams.setHighScores = setHighScores;
    
    NSGS_InitNetwork(&highScoreInitParams);
    
    if (!loadSettings())
        saveSettings();

    game();
    
    NSGS_ShutdownNetwork();
    
    ShutDownTools(refIsHandle, toolStartupRef);
    TOOLFAIL("Unable to shutdown tools");
    
    TLShutDown();
    TOOLFAIL("Unable to shutdown tool locator");
    
    MMShutDown(myUserId);
    TOOLFAIL("Unable to shutdown memory manager");
}
