package com.neptune.modules;

import android.util.Log;

import com.neptune.utils.Convert;
import com.neptune.utils.MyLog;
import com.pax.dal.IPed;
import com.pax.dal.entity.DUKPTResult;
import com.pax.dal.entity.EAesCheckMode;
import com.pax.dal.entity.ECheckMode;
import com.pax.dal.entity.ECryptOperate;
import com.pax.dal.entity.ECryptOpt;
import com.pax.dal.entity.EDUKPTDesMode;
import com.pax.dal.entity.EDUKPTMacMode;
import com.pax.dal.entity.EDUKPTPinMode;
import com.pax.dal.entity.EFuncKeyMode;
import com.pax.dal.entity.EPedDesMode;
import com.pax.dal.entity.EPedKeyType;
import com.pax.dal.entity.EPedMacMode;
import com.pax.dal.entity.EPedType;
import com.pax.dal.entity.EPinBlockMode;
import com.pax.dal.entity.EUartPort;
import com.pax.dal.entity.RSAKeyInfo;
import com.pax.dal.entity.RSARecoverInfo;
import com.pax.dal.entity.SM2KeyPair;
import com.pax.dal.exceptions.PedDevException;


import com.neptune.base.NeptuneAppMgmt;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class Ped {
    private static Ped ped;
    private IPed iPed;
    private byte[] byte_test = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    private KeyPair kp = null;
    public static byte[] modulus1 = null;
    public static boolean isGenRsaKey = false;

    private Ped(EPedType type, int mode) {
        Log.i("Test", type.name());
       if(mode == 0){
           iPed = NeptuneAppMgmt.getDal().getPed(type);
       }else{
           iPed = NeptuneAppMgmt.getDal().getPedKeyIsolation(type);
       }
        if(type == EPedType.EXTERNAL_TYPEA && !NeptuneAppMgmt.getDal().getCommManager().getUartPortList().contains(EUartPort.PINPAD)){
            iPed.setPort(EUartPort.COM1);
        }
        kp = getKeyPair(512);
    }

    public static Ped getInstance(EPedType type, int pedMode) {
        if (ped == null ) {
            ped = new Ped(type, pedMode);
        }
        return ped;
    }

    private KeyPair getKeyPair(int len) {
        KeyPairGenerator kpg = null;
        try {
            kpg = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        SecureRandom random = new SecureRandom();
        kpg.initialize(len, random);
        KeyPair kp = kpg.generateKeyPair();
        return kp;
    }

    // PED writeKey include TMK,TPK,TAK,TDk
    // ===============================================================================================================

    public boolean writeKey(EPedKeyType srcKeyType, byte srcKeyIndex, EPedKeyType destKeyType, byte destkeyIndex,
                            byte[] destKeyValue, ECheckMode checkMode, byte[] checkBuf) throws PedDevException {
        try {
            iPed.writeKey(srcKeyType, srcKeyIndex, destKeyType, destkeyIndex, destKeyValue, checkMode, checkBuf);
            MyLog.logV("writeKey");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    // ===================================================================================================================

    public boolean writeTIK() throws PedDevException {
        byte[] tik16Clr = { (byte) 0x6A, (byte) 0xC2, (byte) 0x92, (byte) 0xFA, (byte) 0xA1, (byte) 0x31, (byte) 0x5B,
                (byte) 0x4D, (byte) 0x85, (byte) 0x8A, (byte) 0xB3, (byte) 0xA3, (byte) 0xD7, (byte) 0xD5, (byte) 0x93,
                (byte) 0x3A };
        byte[] ksn = { (byte) 0xff, (byte) 0xff, (byte) 0x98, (byte) 0x76, (byte) 0x54, (byte) 0x32, (byte) 0x10,
                (byte) 0xE0, (byte) 0x00, (byte) 0x00 };
        try {
            iPed.writeTIK((byte) 0x01, (byte) 0x00, tik16Clr, ksn, ECheckMode.KCV_NONE, null);
            MyLog.logV("writeTIK");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE( e.toString());
            throw e;
        }
    }

    public byte[] getPinBlock(byte keyIndex, String pinsLength, byte[] dataIn, EPinBlockMode mode, int timeout) throws PedDevException {
        try {
            byte[] result = iPed.getPinBlock(keyIndex, pinsLength, dataIn, mode, timeout);
            MyLog.logV("getPinBlock");
            return result;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] getMac(byte[] bytes) throws PedDevException {
        try {
            byte[] bytes_m = iPed.getMac((byte) 2, bytes, EPedMacMode.MODE_00);
            MyLog.logV("getMac");
            return bytes_m;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public byte[] calcDes(byte[] bytes) throws PedDevException {
        try {
            byte[] bytes_d = iPed.calcDes((byte) 3, bytes, EPedDesMode.ENCRYPT);
            MyLog.logV("calcDes");
            return bytes_d;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public DUKPTResult getDUKPTPin(byte[] dataIn) throws PedDevException {
        try {
            DUKPTResult bytes_ped = iPed.getDUKPTPin((byte) 1, "4", dataIn, EDUKPTPinMode.ISO9564_0_INC, 20000);
            MyLog.logV("getDUKPTPin");
            return bytes_ped;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public byte[] getDUKPTMac(byte[] bytes) throws PedDevException {
        try {
            DUKPTResult result = iPed.getDUKPTMac((byte) 0x01, bytes, EDUKPTMacMode.MODE_00);
            if (result != null) {
                MyLog.logV("getDUKPTMac");
                return result.getResult();
            } else {
                MyLog.logV("getDUKPTMac");
                return null;
            }
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    // ==============================================================================
    // 获取 TPK TAK TDK 的KCV
    public byte[] getKCV_TPK() throws PedDevException {
        try {
            byte[] bytes_tpk = iPed.getKCV(EPedKeyType.TPK, (byte) 1, (byte) 0, byte_test);
            MyLog.logV("getKCV_TPK");
            return bytes_tpk;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] getKCV_TAK() throws PedDevException {
        try {
            byte[] bytes_tak = iPed.getKCV(EPedKeyType.TAK, (byte) 2, (byte) 0, byte_test);
            MyLog.logV("getKCV_TAK");
            return bytes_tak;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] getKCV_TDK() throws PedDevException {
        try {
            byte[] bytes_tdk = iPed.getKCV(EPedKeyType.TDK, (byte) 3, (byte) 0, byte_test);
            MyLog.logV("getKCV_TDK");
            return bytes_tdk;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    // =======================================================================================================

    public boolean writeKeyVar(byte[] bs) throws PedDevException {
        try {
            iPed.writeKeyVar(EPedKeyType.TPK, (byte) 1, (byte) 5, bs, ECheckMode.KCV_NONE, new byte[] {});
            MyLog.logV("writeKeyVar");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public String getVersion() throws PedDevException {
        try {
            String str_verString = iPed.getVersion();
            MyLog.logV("getVersion");
            return str_verString;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public boolean erase() throws PedDevException {
        try {
            boolean flag = iPed.erase();
            MyLog.logV("erase");
            return flag;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public boolean setIntervalTime(String num1, String num2) throws PedDevException {
        try {
            iPed.setIntervalTime(Integer.parseInt(num1), Integer.parseInt(num2));
            MyLog.logV("setIntervalTime");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public boolean setFunctionKey(EFuncKeyMode k) throws PedDevException {
        try {
            iPed.setFunctionKey(k);// EFunckeyKeyMode.ClEAR_ALL
            MyLog.logV("setFunctionKey");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE( e.toString());
            throw e;
        }
    }

    public boolean genRsaKey() throws PedDevException {
        try {
            iPed.genRSAKey((byte)2, (byte)1, (short)512, (byte)0);
            MyLog.logV("genRSAKey");
            isGenRsaKey = true;
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            isGenRsaKey = false;
            throw e;
        }
    }

    
    // =====================================================================
    // writeRSAkey include public key and private key
    public boolean writeRSAKeyPublic() throws PedDevException {
        try {
            RSAKeyInfo keyInfo = new RSAKeyInfo();

            RSAPublicKey pubKey = (RSAPublicKey) kp.getPublic();
            byte[] exponent = pubKey.getPublicExponent().toByteArray();
            byte[] exponent1 = exponent;
            if(exponent[0]==0){
                exponent1 = new byte[exponent.length-1];
                System.arraycopy(exponent, 1, exponent1, 0, exponent1.length);
            }
            keyInfo.setExponent(exponent1);
            keyInfo.setExponentLen(exponent1.length * 8 );
            MyLog.logV("RSA public exponent:" + Convert.getInstance().bcdToStr(exponent));
            byte[] modulus = pubKey.getModulus().toByteArray();
            modulus1 = modulus;
            if(modulus[0]== 0){
                modulus1 = new byte[modulus.length-1];
                System.arraycopy(modulus, 1, modulus1, 0, modulus1.length);
            }
            keyInfo.setModulus(modulus1);
            keyInfo.setModulusLen(modulus1.length * 8);
            MyLog.logV("RSA public modulus:" + Convert.getInstance().bcdToStr(modulus));
            keyInfo.setKeyInfo(pubKey.getEncoded());
            MyLog.logV("KeyInfo:"+Convert.getInstance().bcdToStr(pubKey.getEncoded()));
            iPed.writeRSAKey((byte) 1, keyInfo);
            MyLog.logV("writeRSAKey_public");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE( e.toString());
            throw e;
        }
    }

    public RSARecoverInfo RSARecoverPublic(byte[] bytes) throws PedDevException {
        try {
            RSARecoverInfo info = iPed.RSARecover((byte) 1, iPed.RSARecover((byte)2, bytes).getData());
            MyLog.logV("RSARecover_public");
            return info;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public boolean writeRSAKeyPrivate() throws PedDevException {
        try {
            RSAKeyInfo keyInfo = new RSAKeyInfo();

            RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate();
            byte[] exponent = privateKey.getPrivateExponent().toByteArray();
            byte[] exponent1 = exponent;
            if(exponent[0]==0){
                exponent1 = new byte[exponent.length-1];
                System.arraycopy(exponent, 1, exponent1, 0, exponent1.length);
            }
            keyInfo.setExponent(exponent1);
            keyInfo.setExponentLen(exponent1.length * 8);
            MyLog.logV("RSA private exponent:" + Convert.getInstance().bcdToStr(exponent));
            byte[] modulus = privateKey.getModulus().toByteArray();
            modulus1 = modulus;
            if(modulus[0]== 0){
                modulus1 = new byte[modulus.length-1];
                System.arraycopy(modulus, 1, modulus1, 0, modulus1.length);
            }
            keyInfo.setModulus(modulus1);
            keyInfo.setModulusLen(modulus1.length * 8);
            MyLog.logV("RSA private modulus:" + Convert.getInstance().bcdToStr(modulus1));
            keyInfo.setKeyInfo(privateKey.getEncoded());
            MyLog.logV("KeyInfo:"+Convert.getInstance().bcdToStr(privateKey.getEncoded()));
            iPed.writeRSAKey((byte) 2, keyInfo);
            MyLog.logV("writeRSAKey_private");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public RSARecoverInfo RSARecoverPrivate(byte[] bytes) throws PedDevException {
        try {
            RSARecoverInfo info = iPed.RSARecover((byte) 2, iPed.RSARecover((byte)1, bytes).getData());
            MyLog.logV("RSARecover_private");
            return info;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    // =================================================================================
    public byte[] calcDUKPTDesMac(byte[] bytes) throws PedDevException {
        try {
            DUKPTResult result = iPed.calcDUKPTDes((byte) 0x01, (byte) 0x00, null, bytes, EDUKPTDesMode.CBC_ENCRYPTION);
            if (result != null) {
                MyLog.logV("calcDUKPTDes_mac");
                return result.getResult();
            } else {
                MyLog.logV("calcDUKPTDes_mac");
                return null;
            }
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public byte[] calcDUKPTDesDes(byte[] bytes) throws PedDevException {
        try {
            DUKPTResult result = iPed.calcDUKPTDes((byte) 0x01, (byte) 0x01, null, bytes, EDUKPTDesMode.CBC_ENCRYPTION);
            if (result != null) {
                MyLog.logV("calcDUKPTDes_des");
                return result.getResult();
            } else {
                MyLog.logV("calcDUKPTDes_des");
                return null;
            }
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public byte[] getDUKPTKsn() throws PedDevException {
        try {
            byte[] bytes_ksn = iPed.getDUKPTKsn((byte) 1);
            MyLog.logV("getDUKPTKsn");
            return bytes_ksn;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public boolean incDUKPTKsn() {
        try {
            iPed.incDUKPTKsn((byte) 0x01);
            MyLog.logV("incDUKPTKsn");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            return false;
        }
    }

    public void setExMode(byte b) {
        iPed.setExMode(b);
        MyLog.logV("setExMode");
    }

    public boolean clearScreen() throws PedDevException {
        try {
            iPed.clearScreen();
            MyLog.logV("clearScreen");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public String inputStr_1(int num1, int num2) throws PedDevException {
        try {
            String str = iPed.inputStr((byte) 0x00, (byte) num1, (byte) num2, 10000);
            MyLog.logV("inputStr_1");
            return str;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public String inputStr_2(int num1, int num2) throws PedDevException {
        try {
            String str = iPed.inputStr((byte) 0x01, (byte) num1, (byte) num2, 10000);
            MyLog.logV("inputStr_2");
            return str;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public boolean showStr(String str) throws PedDevException {
        try {
            iPed.showStr((byte) 0x00, (byte) 0x00, str);
            MyLog.logV("showStr");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public String getSN() {
        String sn = null;
        try {
            sn = iPed.getSN();
        } catch (PedDevException e) {
            e.printStackTrace();
        }
        MyLog.logV("getSN");
        return sn == null ? "null" : sn;
    }

    public void showInputBox(boolean flag, String title) throws PedDevException {
        try {
            iPed.showInputBox(flag, title);
            MyLog.logV("showInputBox");
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public SM2KeyPair genSM2KeyPair(short keyLenBit) throws PedDevException {
        try {
            SM2KeyPair keyPair = iPed.genSM2KeyPair(keyLenBit);
            MyLog.logV("genSM2KeyPair");
            return keyPair;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public boolean writeSM2CipherKey(EPedKeyType srcKeyType, byte srcKeyIdx, EPedKeyType dstKeyType, byte dstKeyIdx,
                                     byte[] keyValue) throws PedDevException {
        try {
            iPed.writeSM2CipherKey(srcKeyType, srcKeyIdx, dstKeyType, dstKeyIdx, keyValue);
            MyLog.logV("writeSM2CipherKey");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }

    }

    public boolean writeSM2Key(byte keyIdx, EPedKeyType keyType, byte[] keyValue) throws PedDevException {
        try {
            iPed.writeSM2Key(keyIdx, keyType, keyValue);
            MyLog.logV("writeSM2Key");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] SM2Recover(byte keyIdx, byte[] input, ECryptOperate operation) throws PedDevException {
        byte[] res = null;
        try {
            res = iPed.SM2Recover(keyIdx, input, operation);
            MyLog.logV("SM2Recover");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] SM2Sign(byte pubKeyIdx, byte pvtKeyIdx, byte[] uid, byte[] input) throws PedDevException {
        byte[] res = null;
        try {
            res = iPed.SM2Sign(pubKeyIdx, pvtKeyIdx, uid, input);
            MyLog.logV("SM2Sign");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public boolean SM2Verify(byte pubKeyIdx, byte[] uid, byte[] input, byte[] signature) throws PedDevException {
        try {
            iPed.SM2Verify(pubKeyIdx, uid, input, signature);
            MyLog.logV("SM2Verify");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] SM3(byte[] input) throws PedDevException {
        byte[] res = null;
        try {
            res = iPed.SM3(input, (byte) 0);
            MyLog.logV("SM3");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] SM4(byte keyIdx, byte[] initVector, byte[] input, ECryptOperate operation, ECryptOpt option) throws PedDevException {
        byte[] res = null;
        try {
            res = iPed.SM4(keyIdx, initVector, input, operation, option);
            MyLog.logV("SM4");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] getMacSM(byte keyIdx, byte[] initVector, byte[] input, byte mode) throws PedDevException {
        byte[] res = null;
        try {
            res = iPed.getMacSM(keyIdx, initVector, input, mode);
            MyLog.logV("getMacSM");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }

    public byte[] getPinBlockSM4(byte keyIndex, String expPinLen, byte[] dataIn, EPinBlockMode mode, int timeoutMs) {
        byte[] res = null;
        try {
            res = iPed.getPinBlockSM4(keyIndex, expPinLen, dataIn, mode, timeoutMs);
            MyLog.logV("getPinBlockSM4");
            return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
        }
        return res;
    }

    public void setKeyboardLayoutLandscape(boolean landscape) throws PedDevException {
        try {
            iPed.setKeyboardLayoutLandscape(landscape);
            MyLog.logV("setKeyboardLayoutLandscape");
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }
    
    public boolean writeAesKey(EPedKeyType srcKeyType, byte srcKeyIndex, byte destkeyIndex,
                               byte[] destKeyValue, EAesCheckMode checkMode, byte[] checkBuf) throws PedDevException {
        try {
            iPed.writeAesKey(srcKeyType, srcKeyIndex, destkeyIndex, destKeyValue, checkMode, checkBuf);
            MyLog.logV("writeAesKey");
            return true;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }
    
    public byte[] calcAes(byte keyIdx, byte[] initvector, byte[] dataIn, ECryptOperate operation, ECryptOpt option) throws PedDevException {
        try {
           byte[] res = iPed.calcAes(keyIdx, initvector, dataIn, operation, option);
           MyLog.logV("calcAes");
           return res;
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }
    
    public void setKeyboardRandom(boolean flag) throws PedDevException {
        try {
            iPed.setKeyboardRandom(flag);
            MyLog.logV("setKeyboardRandom");
        } catch (PedDevException e) {
            e.printStackTrace();
            MyLog.logE(e.toString());
            throw e;
        }
    }
}
