
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <cstring>

#include "genji.h"
#include "timer.h"
#include "util.h"

#define TRUE  1
#define FALSE 0

int32_t UserCode = 1234;
int16_t PortNr = 85;
int16_t DngNr = 1;

bool FindGenji()
{
    Init_MatrixAPI();

    short nCount = Dongle_Count(85);
    if (nCount < 1)
    {
        std::cout << "Genjiが見つかりません:" << nCount << std::endl;
        Release_MatrixAPI();
        return false;
    }
    else
        std::cout << "Genjiが見つかりました:" << nCount << std::endl;

    std::cout << "SerNr=" << Dongle_ReadSerNr(1234, 1, 85) << std::endl;


    return true;
}

bool LEDTest()
{
    std::cout << "\n***** LED制御 *****" << std::endl;

    std::cout << "LED消灯" << std::endl;
    if (1 != Dongle_SetLED(0, DngNr, PortNr))
        return false;
    pause();

    std::cout << "LED点滅" << std::endl;
    if (1 != Dongle_SetLED(3, DngNr, PortNr))
        return false;
    pause();

    std::cout << "LED点灯" << std::endl;
    if (1 != Dongle_SetLED(-1, DngNr, PortNr))
        return false;
    pause();

    std::cout << "LED消灯" << std::endl;
    if (1 != Dongle_SetLED(0, DngNr, PortNr))
        return false;
    pause();

    std::cout << "LED点灯" << std::endl;
    if (1 != Dongle_SetLED(-1, DngNr, PortNr))
        return false;

    std::cout << "**********" << std::endl;
    pause();

    return true;
}

bool MemSizeGUSNTest()
{
    std::cout << "\n***** Genjiの SN *****" << std::endl;

    unsigned char szGUSN[24];
    memset(szGUSN,0,24);

    short ret;
    if (1 == (ret = Dongle_ReadGUSN(UserCode, szGUSN, DngNr, PortNr)))
        std::cout << "GUSN:" << szGUSN << std::endl;
    else
    {
        std::cout << "GUSN取得エラー: " << ret << std::endl;
        return false;
    }

    std::cout << "**********" << std::endl;

    pause();
    return true;
}

bool Data2Test()
{
    std::cout << "\n***** データ領域#2 *****" << std::endl;

    short memSize2 = Dongle_MemSize2(DngNr, PortNr);
    if (memSize2 > 0)
        std::cout << "サイズ : " << memSize2 << " バイト" << std::endl << std::endl;
    else
        return false;

    pause();

    std::cout << "書込むデータを入力後、RETURN\a" << std::endl;

    std::string s;
    if (std::getline(std::cin, s)) {

        int32_t dataLen = (int32_t)s.length();

        if (dataLen + 4 > memSize2)
        {
            std::cout << "データが長すぎます" << std::endl;
            return false;
        }

        std::unique_ptr<unsigned char[]> data = std::make_unique<unsigned char[]>(dataLen + sizeof(int32_t));

        memcpy(&data[0], &dataLen, sizeof(int32_t));
        memcpy(&data[sizeof(int32_t)], s.c_str(), dataLen);

        short ret;
        if ((ret = Dongle_WriteData2(UserCode, data.get(), 0, (int16_t)(dataLen + sizeof(int32_t)), DngNr, PortNr)) < 0)
        {
            std::cout << "書き込み失敗: " << ret << std::endl;
            return false;
        }

        std::cout << "\n書き込みました(データ長 " << sizeof(int32_t) << "バイト +  データ " << dataLen << ") =" << ret << std::endl << std::endl;
        std::cout << "次に読み込みます" << std::endl;
        pause();

        //読込
        dataLen = 0;
        if ((ret = Dongle_ReadData2(UserCode, (unsigned char*)&dataLen, 0, sizeof(int32_t), DngNr, PortNr)) < 0)
        {
            std::cout << "データ長の読み込み失敗: " << ret << std::endl;
            return false;
        }

        std::cout << "データ長読込: " << dataLen << std::endl;

        std::unique_ptr<unsigned char[]> data2 = std::make_unique<unsigned char[]>(dataLen + 1);
        data2[dataLen] = 0;

        if ((ret = Dongle_ReadData2(UserCode, data2.get(), sizeof(int32_t), dataLen, DngNr, PortNr)) < 0)
        {
            std::cout << "データ読み込み失敗: " << ret << std::endl;
            return false;
        }

        std::cout << "データ読み込みました: " << ret << std::endl;
        std::cout << data2.get() << std::endl;

    }
    else
        return false;

    std::cout << "**********" << std::endl << std::endl;
    pause();

    return true;
}

bool CreateRSAKey(int keyIdx)
{
    short    ret;
    double   interval;

    std::unique_ptr<CTimer> timer = std::make_unique<CTimer>();

    std::cout << "\n***** RSAキー生成(#" << keyIdx << ")*****" << std::endl;

    timer->start();

    ret = Dongle_CreateRSAKeyPair(UserCode, keyIdx, DngNr, PortNr);

    interval = timer->interval();
    timer->stop();

    if (ret != 1)
    {
        std::cout << "キーペア生成エラー: " << ret << std::endl;
        return false;
    }

    std::cout << "\nooooo キーペア(#" << keyIdx << ")を生成しました ooooo" << std::endl;
    std::cout << "キーペア(#" << keyIdx << ")生成時間:" << interval << " millsec" << std::endl;
    pause();
    
    return true;
}

bool RSAEncryptTest(int keyIdx)
{
    short    ret;
    double   interval;

    std::cout << "\n***** 暗号化/復号化(#" << keyIdx << ") *****" << std::endl;

    std::unique_ptr<CTimer> timer = std::make_unique<CTimer>();

retry:
    std::cout << "\n暗号化するデータ(最大117バイト)を入力後、RETURN" << std::endl;

    std::string s;
    if (std::getline(std::cin, s)) {

        trim(s);
        int32_t dataLen = (int32_t)s.length();

        if (dataLen == 0) goto retry;

        if (dataLen > 117)
        {
            std::cout << "データが長すぎます" << std::endl;
            return false;
        }

        unsigned char cipher[250];
        short cipherLen = 250;

        timer->start();

        ret = Dongle_EncryptDataRSA(UserCode, keyIdx, (const unsigned char*)s.c_str(), (short)s.length(), cipher, &cipherLen, DngNr, PortNr);

        interval = timer->interval();
        timer->stop();

        if (ret != 1)
        {
            std::cout << "\n暗号化エラー: " << ret << std::endl;
            return false;
        }

        std::cout << "\nooooo 暗号化しました ooooo" << std::endl;
        std::cout << "暗号化にかかった時間:" << interval << "msec" << std::endl;

        //暗号化データ（HEX）の表示
        printHex(cipher, cipherLen);
        pause();

        short plainTextLen = 128;
        unsigned char plainText[128];

        timer->start();

        ret = Dongle_DecryptDataRSA(UserCode, keyIdx, cipher, cipherLen, plainText, &plainTextLen, DngNr, PortNr);

        interval = timer->interval();
        timer->stop();

        if (ret == 1)
        {
            std::cout << "\nooooo 復号化しました ooooo" << std::endl;
            std::cout << "復号化にかかった時間t=" << interval << "msec" << std::endl;

            plainText[plainTextLen] = 0;
            std::cout << plainText << std::endl;
            pause();

        }
        else
            std::cout << "復号化エラー" << std::endl;
    }
    else
        return false;


    return true;
}

bool RSASignTest(int keyIdx)
{
    short ret = 0;
    double interval;

    std::unique_ptr<CTimer> timer = std::make_unique<CTimer>();

    std::cout << "\n***** 署名/検証(#" << keyIdx << ")*****" << std::endl;

retry:
    std::cout << "\n署名するデータを入力後、RETURN\a" << std::endl;

    std::string s;
    if (std::getline(std::cin, s)) {

        trim(s);
        int32_t dataLen = (int32_t)s.length();
        if (dataLen == 0) goto retry;

        unsigned char sig[250];
        short sigLen = 250;

        timer->start();

        ret = Dongle_SignDataRSA(UserCode, keyIdx, (unsigned char*)s.c_str(), dataLen, sig, &sigLen, DngNr, PortNr);

        interval = timer->interval();
        timer->stop();

        if (ret != 1)
        {
            std::cout << "\n署名エラー: " << ret << std::endl;
            return false;
        }

        std::cout << "\nooooo 署名しました ooooo" << std::endl;
        std::cout << "署名にかかった時間=" << interval << "msec" << std::endl;

        //HEX表示
        printHex(sig, sigLen);
        pause();


        //署名検証
        timer->start();

        ret = Dongle_VerifySigRSA(UserCode, keyIdx, (unsigned char*)s.c_str(), dataLen, sig, sigLen, DngNr, PortNr);

        interval = timer->interval();
        timer->stop();

        if (ret != 1)
        {
            std::cout << "\n署名検証エラー: " << ret << std::endl;
            return false;
        }

        std::cout << "\n検証 Ok" << std::endl;
        std::cout << "検証にかかった時間=" << interval << "msec" << std::endl;
        pause();

    }
    else
        return false;

    return true;

}

bool writeRSAKey( bool bPrivKey, int rsaKeyIdx, const char* filename)
{
    FILE* fp = fopen( filename, "rb");

    if (fp != NULL)
    {
        fseek(fp, 0, SEEK_END);
        long fsize = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        std::unique_ptr<unsigned char[]> data = std::make_unique<unsigned char[]>(fsize);

        size_t bytesRead = fread(data.get(), 1, fsize, fp);
        if (bytesRead != static_cast<size_t>(fsize)) {
            std::cerr << "Read error: expected " << fsize
                << " bytes, got " << bytesRead << " bytes\n";
            fclose(fp);
            return false ;
        }

        fclose(fp);

        short ret = Dongle_WriteRSAKey(UserCode, rsaKeyIdx, bPrivKey==true ? TRUE : FALSE, fsize, data.get(), 1, 85);
        if (ret == 1)
        {
            std::cout << filename << " を Genji に書き込みました" << std::endl;
            return true;
        }
        else
            std::cout << filename << " を Genji に書き込みできませんでした" << std::endl;

    }
    else
        std::cout << "ファイル " << filename << " オープンエラー" << std::endl;

    return false;
}

bool RSAKeyImport(int keyIdx)
{
    std::cout << "\n***** 外部キーペアのインポート*****" << std::endl;
    
    if (false == writeRSAKey(true, keyIdx, "genji.privkey")) return false;
    if (false == writeRSAKey(false, keyIdx, "genji.pubkey")) return false;
    
    pause();
    return true;
    
}

bool RandTest()
{
    short ret = 0;
    double interval;

    std::unique_ptr<CTimer> timer = std::make_unique<CTimer>();

    std::cout << "\n***** 乱数 *****" << std::endl;

    unsigned char byteRand[32];
    short randLen = 32;

    for (int i = 0; i < 10; i++)
    {
        std::cout << "\n>> " << i + 1 << " 回目" << std::endl;
        timer->start();

        ret = Dongle_GetRand(UserCode, randLen, byteRand, DngNr, PortNr);

        interval = timer->interval();
        timer->stop();

        if (ret == 1)
        {

            std::cout << "oooo 乱数生成 ooooo" << std::endl;
            std::cout << "乱数生成にかかった時間t=" << interval << "msec" << std::endl;
            printHex(byteRand, randLen);
        }
        else
        {
            std::cout << "乱数生成エラー: " << ret << std::endl;
            return false;
        }

        pause(1000);
    }

    return true;
}

bool RTCTimeTest()
{
    std::cout << "\n***** RTC時間 *****" << std::endl;
    
    struct tm rtcTime;
    short ret = Dongle_GetTime(UserCode, &rtcTime, DngNr, PortNr );
    if( ret == 1)
    {
        std::cout << rtcTime.tm_year+1900 << "年" << rtcTime.tm_mon+1 << "月" << rtcTime.tm_mday << "日　"  << rtcTime.tm_hour << "時" << rtcTime.tm_min << "分" << rtcTime.tm_sec << "秒";
        return true;
    }
    else
    {
        if( ret == -10)
            std::cout << " <<RTC未搭載>>" << std::endl;
        else
            std::cout << "GetTimeエラー:" << ret << std::endl;
    }
    
    return false;
}

void cleanup()
{
    std::cout << "\n\n<<<<< デモ終了 >>>>>" << std::endl;
    std::cout << "RETURN でプログラム終了" << std::endl;
    (void)getchar();

    Release_MatrixAPI();
}
