#include "touchcal.h"
#include "ui.h"
#include "hid.h"
#include "crc16.h"
#include "language.h"
#include "spiflash.h"
#include "support.h"


#define TOUCH_CALIB_FILENAME    "gm9calib.bin"


static const HID_CalibrationData default_calib = {
    .screen_x = 0,
    .screen_y = 0,
    .ts_raw = 0
    // ^ wrong: in my console it's 0x780086
    // but this is very much console dependent
    // so it's better to go with a sane default
};

static bool is_calibrated = false;


static bool SetCalibrationDefaults(void)
{
    // Hardcoding this isn't ideal but it's better than
    // leaving the system without any state to work with
    is_calibrated = false; // no, this is not proper calibration
    return HID_SetCalibrationData(&default_calib, 1, SCREEN_WIDTH_BOT, SCREEN_HEIGHT);
}

bool ShowTouchCalibrationDialog(void)
{
    static const u32 dot_positions[][2] = {
        {16, 16},
        {SCREEN_WIDTH_BOT - 16, SCREEN_HEIGHT - 16},
        {16, SCREEN_HEIGHT - 16},
        {SCREEN_WIDTH_BOT - 16, 16},
    };

    HID_CalibrationData calibrations[countof(dot_positions)];
    for (u32 i = 0; i < countof(dot_positions); i++) {
        calibrations[i].screen_x = dot_positions[i][0];
        calibrations[i].screen_y = dot_positions[i][1];
    }

    // clear screen, draw instructions
    ClearScreen(BOT_SCREEN, COLOR_STD_BG);
    DrawStringCenter(BOT_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s",
        STR_TOUCH_CROSSHAIRS_TO_CALIBRATE_TOUCHSCREEN_USE_STYLUS);

    // set calibration defaults
    SetCalibrationDefaults();

    // actual calibration
    for (u32 current_dot = 0; current_dot < countof(dot_positions); current_dot++) {
        // draw four crosshairs
        for (u32 i = 0; i < countof(dot_positions); i++) {
            int color_cross = (i < current_dot) ? COLOR_BRIGHTGREEN : (i == current_dot) ? COLOR_RED : COLOR_STD_FONT;
            for (u32 r = 2; r < 8; r++) {
                DrawPixel(BOT_SCREEN, dot_positions[i][0] + 0, dot_positions[i][1] + r, color_cross);
                DrawPixel(BOT_SCREEN, dot_positions[i][0] + r, dot_positions[i][1] + 0, color_cross);
                DrawPixel(BOT_SCREEN, dot_positions[i][0] + 0, dot_positions[i][1] - r, color_cross);
                DrawPixel(BOT_SCREEN, dot_positions[i][0] - r, dot_positions[i][1] + 0, color_cross);
            }
        }

        // wait for input, store calibration data
        while (1) {
            u32 pressed = InputWait(0);
            if (pressed & BUTTON_B) {
                return false;
            } else if (pressed & BUTTON_TOUCH) {
                calibrations[current_dot].ts_raw = HID_ReadRawTouchState();
                break;
            }
        }
    }

    is_calibrated = HID_SetCalibrationData(calibrations, countof(dot_positions), SCREEN_WIDTH_BOT, SCREEN_HEIGHT);
    if (is_calibrated) { // store calibration data in a file
        SaveSupportFile(TOUCH_CALIB_FILENAME, calibrations, sizeof(calibrations));
    }
    return is_calibrated;
}

bool CalibrateTouchFromSupportFile(void) {
    HID_CalibrationData calibrations[10];
    size_t size = LoadSupportFile(TOUCH_CALIB_FILENAME, calibrations, sizeof(calibrations));
    u32 n_dots = size / sizeof(HID_CalibrationData);

    is_calibrated = (n_dots == 0) ? false :
        HID_SetCalibrationData(calibrations, n_dots, SCREEN_WIDTH_BOT, SCREEN_HEIGHT);
    return is_calibrated;
}

bool CalibrateTouchFromFlash(void) {
    HID_CalibrationData data[2];

    // set calibration defaults
    SetCalibrationDefaults();

    // check NVRAM console ID
    u32 console_id = 0;
    if (!spiflash_read(0x001C, 4, (u8*)&console_id))
        return false;

    if (((console_id >> 8) & 0xFF) != 0x57)
        return false; // not a 3DS

    // read and check DS fw user settings
    // see: https://problemkaputt.de/gbatek.htm#dsfirmwareusersettings
    u32 fw_usercfg_buf[0x100 / 0x4];
    u8* fw_usercfg = (u8*) fw_usercfg_buf;
    if (!spiflash_read(0x1FE00, 0x100, fw_usercfg))
        return false;

    if (getle16(fw_usercfg + 0x72) != crc16_quick(fw_usercfg, 0x70))
        return false;

    // get touchscreen calibration data from user settings
    u8 *ts_data = fw_usercfg + 0x58;
    for (int i = 0; i < 2; i++) {
        int base = i * 6;
        data[i].ts_raw = ts_data[base + 1] << 24 | ts_data[base + 0] << 16 | ts_data[base + 3] << 8 | ts_data[base + 2];
        data[i].screen_x = (((int)ts_data[base + 4]) * SCREEN_WIDTH_BOT) / 256;
        data[i].screen_y = (((int)ts_data[base + 5]) * SCREEN_HEIGHT) / 192;
    }

    is_calibrated = HID_SetCalibrationData(data, 2, SCREEN_WIDTH_BOT, SCREEN_HEIGHT);
    return is_calibrated;
}

bool TouchIsCalibrated(void) {
    return is_calibrated;
}
