package demo;
import jp.co.ribig.genji.ELGenji;
import jp.co.ribig.genji.ELTimer;


import java.io.IOException;
import java.nio.charset.StandardCharsets;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.DataInputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;


public class Demo {

    short DngNr = 1;
    short PortNr = 85;
    int   UserCode = 1234;

    void pause()
    {
        try{
        Thread.sleep(2000);
        }
        catch( InterruptedException ex)
        {
            System.out.println( ex.getMessage() );
        }
    }

    //RTC内蔵ELのみ
    boolean GetRTCTime()
    {
        ELGenji.tm _tm = new ELGenji.tm();

        short r =  ELGenji.Dongle_GetTime(UserCode, _tm, DngNr, PortNr);
        if( r == 1)
            System.out.println((_tm.tm_year+1900) + "年" + (_tm.tm_mon+1) + "月" + _tm.tm_mday + "日" + _tm.tm_hour + "時" + _tm.tm_min + "分" + _tm.tm_sec + "秒");
        else 
        {
            if( r== -10)
                System.out.println("xxx RTC未搭載 xxx");
            else
                System.out.println("get time error:" + r);
            return false;
        }

        return true;
    }

    boolean FindGenji()
    {
        short nCount = ELGenji.Dongle_Count(PortNr);
        if (nCount < 1)
        {
            System.out.println( "\nGenjiが見つかりません:" + nCount );
            return false;
        }
        else
            System.out.println( "\nGenjiが見つかりました:" + nCount );

        int ret = ELGenji.Dongle_ReadSerNr(UserCode, DngNr, PortNr);
        if( ret < 1)
        {
            System.out.println("シリアル番号取得エラー:" + ret );
            return false;
        }

        System.out.println("SerNr = " + ret );

       return true;
    }

    boolean LEDTest()
    {
        System.out.println( "\n***** LED制御 *****"  );

        System.out.println( "LED消灯"  );
        if (1 != ELGenji.Dongle_SetLED((short)0, DngNr, PortNr))
            return false;
        pause();

        System.out.println( "LED点滅"  );
        if (1 != ELGenji.Dongle_SetLED((short)3, DngNr, PortNr))
            return false;
        pause();

        System.out.println( "LED点灯"  );
        if (1 != ELGenji.Dongle_SetLED((short)-1, DngNr, PortNr))
            return false;
        pause();

        System.out.println( "LED消灯"  );
        if (1 != ELGenji.Dongle_SetLED((short)0, DngNr, PortNr))
            return false;
        pause();

        System.out.println( "LED点灯"  );
        if (1 != ELGenji.Dongle_SetLED((short)-1, DngNr, PortNr))
            return false;

        System.out.println( "**********"  );
        pause();

        return true;
    }

    boolean GUSNTest()
    {
        System.out.println( "\n***** Genjiの SN *****"  );

        byte[] szGUSN = new byte[24];

        short ret;
        if (1 == (ret = ELGenji.Dongle_ReadGUSN(UserCode, szGUSN, DngNr, PortNr)))
        {
           System.out.println( "GUSN=" + new String(szGUSN,  StandardCharsets.UTF_8)  );
        }
        else
        {
            System.out.println( "***** GUSN 取得エラー: " + ret + " *****"  );
            return false;
        }

        System.out.println( "**********"  );

        pause();
        return true;
    }

    byte[] IntToByteArray( int data ) {    
        byte[] result = new byte[4];
        result[0] = (byte) ((data & 0xFF000000) >> 24);
        result[1] = (byte) ((data & 0x00FF0000) >> 16);
        result[2] = (byte) ((data & 0x0000FF00) >> 8);
        result[3] = (byte) ((data & 0x000000FF) >> 0);
        return result;        
    }

    int fromByteArray(byte[] bytes) {
        return ((bytes[0] & 0xFF) << 24) | 
                ((bytes[1] & 0xFF) << 16) | 
                ((bytes[2] & 0xFF) << 8 ) | 
                ((bytes[3] & 0xFF) << 0 );
    }

    boolean Data2Test()
    {
        short ret;

        System.out.println( "\n***** データ領域#2 *****"  );

        short memSize2 = ELGenji.Dongle_MemSize2(DngNr, PortNr);
        if (memSize2 > 0)
            System.out.println( "サイズ : " + memSize2 + " バイト"   );
        else
            return false;

        pause();

        System.out.println( "書込むデータを入力後、RETURN" );

        String str = null;

        try {

            InputStreamReader isReader = new InputStreamReader(System.in,"Shift-JIS");
            BufferedReader bReader = new BufferedReader(isReader);

            str = bReader.readLine();

            int dataLen = 0;
            if( str == null) return false;
            dataLen = str.length();

            if (dataLen + 4 > memSize2)
            {
                System.out.println( "データが長すぎます" );
                return false;
            }
            else if( dataLen==0)
            {
                System.out.println( "データテストスキップ" );
                return true;
            }

            //Write
            {
                byte[] byteData = str.getBytes(StandardCharsets.UTF_8);
                byte[] byteLen = IntToByteArray( byteData.length );

                // concat byteLen and bytesData
                byte[] byteWrite = new byte[byteLen.length + byteData.length];
                System.arraycopy(byteLen, 0, byteWrite, 0, byteLen.length);
                System.arraycopy(byteData, 0, byteWrite, byteLen.length, byteData.length);

                int writeLen =  (byteLen.length + byteData.length) ;
                System.out.println( "write len=" + writeLen);

                ret =ELGenji.Dongle_WriteData2(UserCode, byteWrite, (short)0, (short)(byteLen.length + byteData.length), DngNr, PortNr);
                if( ret  < 0)
                {
                    System.out.println( "書き込み失敗" );
                    return false;
                }

                System.out.println( "\n書き込みました(データ長 " + byteLen.length+ "バイト +  データ " + byteData.length + "バイト) =" + ret );
                System.out.println( "次に読み込みます" );
                pause();
            }

            //読込
            {
                byte[] byteLen = new byte[4];
                if ((ret = ELGenji.Dongle_ReadData2(UserCode, byteLen, (short)0, (short)4, DngNr, PortNr)) < 0)
                {
                    System.out.println( "データ長の読み込み失敗: " +  ret );
                    return false;
                }

                dataLen = fromByteArray(byteLen);

                System.out.println( "データ長読込: " +  dataLen );

                byte[] byteData = new byte[dataLen];

                if ((ret = ELGenji.Dongle_ReadData2(UserCode, byteData, (short)4, (short)dataLen, DngNr, PortNr)) < 0)
                {
                    System.out.println( "データ読み込み失敗: " +  ret );
                    return false;
                }

                System.out.println( "データ読み込みました: "  +  ret );
                System.out.println( new String(byteData,  StandardCharsets.UTF_8) );

            }

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }


       System.out.println( "**********"  );
       pause();

        return true;
    }

    void printHex(byte[] a, short len)
    {
        char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

        char[] hexChars = new char[len * 2];
        for (int j = 0; j < len; j++) {
            int v = a[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        System.out.println(hexChars);
    }

    boolean RSACreateKeyPair(short idx)
    {
        short    ret;
        double   interval;

        ELTimer timer = new ELTimer(UserCode,DngNr);

        System.out.println( "\n***** RSA暗号化/復号化 *****" );

        timer.start();

        ret = ELGenji.Dongle_CreateRSAKeyPair(UserCode, idx, DngNr, PortNr);

        interval = timer.interval();
        timer.stop();

        if (ret != 1)
        {
            System.out.println( "キーペア生成エラー: " +  ret );
            return false;
        }

        System.out.println( "\nooooo キーペア(#" + idx + ")を生成しました ooooo" );
        System.out.println( "キーペア(#" + idx + ")生成時間:" + interval + " millsec" );
        pause();

        return true;
    }

    boolean RSAEncryptTest(short idx)
    {
        short    ret;
        double   interval;

        ELTimer timer = new ELTimer(UserCode,DngNr);

        System.out.println( "\n***** RSA暗号化/復号化 (キーペア #" + idx + ")*****" );

        System.out.println(  "\n暗号化するデータ(最大117バイト)を入力後、RETURN" );

        String str = null;

        try {

            InputStreamReader isReader = new InputStreamReader(System.in,"Shift-JIS");
            BufferedReader bReader = new BufferedReader(isReader);

            str = bReader.readLine();

            int dataLen = 0;
            if( str == null) return false;
            dataLen = str.length();

            if (dataLen == 0) 
            {
                System.out.println("データ未入力.暗号化テストをスキップします");
                return true;
            }

            if (dataLen > 117)
            {
                System.out.println( "データが長すぎます" );
                return false;
            }

            byte[] plain = str.getBytes(StandardCharsets.UTF_8);
            short plainLen = (short)plain.length;

            byte[] cipher = new byte[250];
            short[] cipherLen = new short[1];
            cipherLen[0]= 250;

            timer.start();

            ret = ELGenji.Dongle_EncryptDataRSA(UserCode, idx, plain, plainLen, cipher, cipherLen, DngNr, PortNr);

            interval = timer.interval();
            timer.stop();

            if (ret != 1)
            {
                System.out.println( "\n暗号化エラー: " + ret );
                return false;
            }

            System.out.println( "\nooooo 暗号化しました ooooo" );
            System.out.println( "暗号化にかかった時間:" + interval + "msec" );

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

            short[] plainTextLen = new short[1];
            plainTextLen[0] = 128;

            byte [] plainText = new byte[128];

            timer.start();

            ret = ELGenji.Dongle_DecryptDataRSA(UserCode, idx, cipher, cipherLen[0], plainText, plainTextLen, DngNr, PortNr);

            interval = timer.interval();
            timer.stop();

            if (ret == 1)
            {
                System.out.println("\nooooo 復号化しました ooooo" );
                System.out.println( "復号化にかかった時間=" + interval + "msec" );

                System.out.println( new String(plainText,  StandardCharsets.UTF_8) );
                pause();

            }
            else
                System.out.println( "復号化エラー" );


        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }


        return true;
    }

    boolean RSASignTest(short idx)
    {
        short ret = 0;
        double interval;

        ELTimer timer =  new ELTimer(UserCode, DngNr);

        System.out.println(  "\n***** 署名/検証 (キーペア #" + idx + ") *****"  );

    retry:
        System.out.println(  "\n署名するデータを入力後、RETURN" );

           String str = null;

        try {

            InputStreamReader isReader = new InputStreamReader(System.in,"Shift-JIS");
            BufferedReader bReader = new BufferedReader(isReader);

            str = bReader.readLine();

            int strLen = 0;
            if( str == null) return false;
            strLen = str.length();

            if (strLen == 0) 
            {
                System.out.println("データ未入力.署名テストをスキップします");
                return true;
            }

            byte[] data = str.getBytes(StandardCharsets.UTF_8);
            short  dataLen = (short)data.length;

            byte[] sig = new byte[250];
            short[] sigLen = new short[1];
            sigLen[0] = 250;

            timer.start();

            ret = ELGenji.Dongle_SignDataRSA(UserCode, idx, data, dataLen, sig, sigLen, DngNr, PortNr);

            interval = timer.interval();
            timer.stop();

            if (ret != 1)
            {
                System.out.println( "\n署名エラー: " + ret);
                return false;
            }

            System.out.println( "\nooooo 署名しました ooooo" );
            System.out.println( "署名にかかった時間t=" + interval + "msec" );

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


            //署名検証
            timer.start();

            ret = ELGenji.Dongle_VerifySigRSA(UserCode, idx, data, dataLen, sig, sigLen[0], DngNr, PortNr);

            interval = timer.interval();
            timer.stop();

            if (ret != 1)
            {
                System.out.println( "\n署名検証エラー: " + ret);
                return false;
            }

            System.out.println( "\n検証 Ok" );
            System.out.println( "検証にかかった時間=" + interval + "msec" );
            pause();

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }



        return true;
    }

    //keyType 1 .. private, 0 .. public
    boolean writeRSAKey(short idx, String filename, int keyType)
    {
        short ret;

        try {
            byte[] data = new byte[1024];
            int readByte = 0, totalByte = 0;

            Path filePath = Paths.get(filename);
            if (Files.exists(filePath))
            {
                long fsize = Files.size(filePath);

                DataInputStream dataInStream =
                    new DataInputStream(
                        new BufferedInputStream(
                            new FileInputStream(filename)));

                 readByte = dataInStream.read(data);
                 if( readByte == fsize )
                 {
                     ret = ELGenji. Dongle_WriteRSAKey(UserCode, idx, keyType, (short)fsize, data, DngNr, PortNr);
                     if( ret != 1)
                     {
                        System.out.println(filename + " 書き込みエラー:" + ret);
                        return false;
                     }
                     System.out.println("キーidx #" + idx + "に " + filename + " 書き込み成功");
                 }

                 dataInStream.close();
            }
        }
        catch( Exception ex)
        {
            ex.printStackTrace();
            return false;
        }

        return true;
    }

    boolean RSAKeyTest(short idx)
    {
       System.out.println("\n***** ファイルに保存されたキーペアーの書き込み ****");
       if( writeRSAKey( idx, "genji.privkey", 1) == false ) return false;
       if( writeRSAKey( idx, "genji.pubkey", 0) == false ) return false;
        
       return true;
    }

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

        ELTimer  timer = new ELTimer(UserCode, DngNr);

        System.out.println( "\n***** 乱数 *****" );

        byte[] byteRand = new byte[32];
        short randLen = 32;

        for (int i = 0; i < 10; i++)
        {
            int c=i+1;
            System.out.println ("\n" + c + " 回目");
            timer.start();

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

            interval = timer.interval();
            timer.stop();

            if (ret == 1)
            {
                System.out.println ( "oooo 乱数生成 ooooo" );
                System.out.println ( "乱数生成にかかった時間=" + interval + "msec" );
                printHex(byteRand,randLen);
            }
            else
            {
                System.out.println ( "乱数生成エラー: " + ret );
                return false;
            }

            pause();
        }

        return true;
    }

    public void start()
    {
        short ret;

        ELGenji.Init_MatrixAPI();

        //Count
        if( FindGenji() == false ) return;

        if( LEDTest() == false ) return;       
        if( GUSNTest() == false ) return;
        if( Data2Test() == false ) return;

        // key idx 1 にキーペア―を作成、そのキーで暗号化,署名テスト
        if( RSACreateKeyPair((short)1) ==false ) return;
        if( RSAEncryptTest((short)1) == false ) return;
        if( RSASignTest((short)1) == false ) return;

        // key idx 2 にキーペア―をファイルから読み込んで、書き込み,
        // 書き込みが成功したら、そのキーで暗号化,署名テスト
        if( RSAKeyTest((short)2) ==false ) return;
        if( RSAEncryptTest((short)2) == false ) return;
        if( RSASignTest((short)2) == false ) return;

        //乱数生成テスト
        if( RandTest() == false ) return;
  
        //RTC時間取得（RTC内蔵ELのみ対応）
        GetRTCTime();

        ELGenji.Release_MatrixAPI();

        System.out.println("\n***** デモ終了 *****\n");

    }

}