/*
 * Copyright (C) 2017 BullyBoo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.bullyboo.encoder.methods;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import ru.bullyboo.encoder.Base64;
import ru.bullyboo.encoder.constants.Constants;
import ru.bullyboo.encoder.constants.Padding;

/**
 * DES Encrypt/Decrypt class
 */
public class DES extends BaseMethod{

    private static final String DES_CFB = "DES/CFB";
    private static final String DES_OFB = "DES/OFB";

    private static final int KEY_LEGHT = 8;
    private static final int VECTOR_LEGHT = 8;

    /**
     * All supported methods
     */
    public enum Method{
        DES_ECB_NoPadding ("DES/ECB/NoPadding"),
        DES_ECB_PKCS5Padding ("DES/ECB/PKCS5Padding"),
        DES_ECB_PKCS7Padding ("DES/ECB/PKCS7Padding"),
        DES_ECB_ISO10126Padding ("DES/ECB/ISO10126Padding"),

        DES_CBC_NoPadding ("DES/CBC/NoPadding"),
        DES_CBC_PKCS5Padding ("DES/CBC/PKCS5Padding"),
        DES_CBC_PKCS7Padding ("DES/CBC/PKCS7Padding"),
        DES_CBC_ISO10126Padding ("DES/CBC/ISO10126Padding"),

        DES_CTR_NoPadding ("DES/CTR/NoPadding"),
        DES_CTR_PKCS5Padding ("DES/CTR/PKCS5Padding"),
        DES_CTR_PKCS7Padding ("DES/CTR/PKCS7Padding"),
        DES_CTR_ISO10126Padding ("DES/CTR/ISO10126Padding"),

        DES_CTS_NoPadding ("DES/CTS/NoPadding"),
        DES_CTS_PKCS5Padding ("DES/CTS/PKCS5Padding"),
        DES_CTS_PKCS7Padding ("DES/CTS/PKCS7Padding"),
        DES_CTS_ISO10126Padding ("DES/CTS/ISO10126Padding"),

        DES_CFB_NoPadding ("DES/CFB/NoPadding"),
        DES_CFB_PKCS5Padding ("DES/CFB/PKCS5Padding"),
        DES_CFB_PKCS7Padding ("DES/CFB/PKCS7Padding"),
        DES_CFB_ISO10126Padding ("DES/CFB/ISO10126Padding"),

        DES_OFB_NoPadding ("DES/OFB/NoPadding"),
        DES_OFB_PKCS5Padding ("DES/OFB/PKCS5Padding"),
        DES_OFB_PKCS7Padding ("DES/OFB/PKCS7Padding"),
        DES_OFB_ISO10126Padding ("DES/OFB/ISO10126Padding");

        private final String method;

        Method(String method) {
            this.method = method;
        }

        public String getMethod() {
            return method;
        }
    }

    static abstract class MethodMode{

        private MethodMode(String method){
            this.method = method;
        }

        static String method;

        static boolean checkNumber(int methodNumber){
            if(methodNumber >= 8 && methodNumber <= 64){
                return true;
            } else {
                throw new IllegalStateException(Constants.METHOD_CFB_OFB_EXCEPTION);
            }
        }

        public String getMethod() {
            return method;
        }
    }

    /**
     * DES-CBF encryption methods
     * This class implements setting of encryption method number
     */
    public static class MethodCFB extends MethodMode {

        private MethodCFB(String method) {
            super(method);
        }

        public static MethodCFB generateMethod(int methodNumber, Padding padding){
            if(checkNumber(methodNumber)){
                return new MethodCFB(DES_CFB + methodNumber + "/" + padding.getPadding());
            } else {
                return null;
            }
        }
    }

    /**
     * DES-OBF encryption methods
     * This class implements setting of encryption method number
     */
    public static class MethodOFB extends MethodMode {

        private MethodOFB(String method) {
            super(method);
        }

        public static MethodOFB generateMethod(int methodNumber, Padding padding){
            if(checkNumber(methodNumber)){
                return new MethodOFB(DES_OFB + methodNumber + "/" + padding.getPadding());
            } else {
                return null;
            }
        }
    }

    /**
     * Implementation of DES encryption
     */
    public static String encrypt(String method, byte[] key, byte[] vector, byte[] message) throws Exception {

//        generate Key
        byte[] keyBytes = generateKey(key, KEY_LEGHT);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, method);

//        generate Initialization Vector
        byte[] keyBytesIv = generateVector(vector, VECTOR_LEGHT);
        IvParameterSpec ivSpec = new IvParameterSpec(keyBytesIv);

        Cipher cipher = Cipher.getInstance(method);

        if(hasInitVector(method)){
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        } else {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        }

        byte[] cipherText = cipher.doFinal(message);

        return Base64.encodeToString(cipherText, Base64.DEFAULT);
    }

    /**
     * Implementation of DES decryption
     */
    public static String decrypt(String method, byte[] key, byte[] vector, byte[] message) throws Exception{

//        generate Key
        byte[] keyBytes = generateKey(key, KEY_LEGHT);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, AES.Method.AES.getMethod());

//        generate Initialization Vector
        byte[] keyBytesIv = generateVector(vector, VECTOR_LEGHT);
        IvParameterSpec ivSpec = new IvParameterSpec(keyBytesIv);

        Cipher cipher = Cipher.getInstance(method);

        if(hasInitVector(method)){
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        } else {
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
        }

        byte[] cipherText = cipher.doFinal(Base64.decode(message, Base64.DEFAULT));

        return new String(cipherText);
    }
}