package com.airlock.iam.security.util.crypto;

import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.util.Arrays;

final class CipherText {
   private static final byte PREFIX_START = 91;
   private static final byte PREFIX_END = 93;
   private final byte[] ciphertext;
   private String kdfPrefix;
   private String cipherPrefix;

   static CipherText createFromCiphertextWithPotentialPrefixes(byte[] ciphertext, int maxPossiblePrefixes) throws CipherText.InvalidPrefixException {
      Preconditions.checkArgument(maxPossiblePrefixes >= 0 && maxPossiblePrefixes <= 2, "The supported maximum number of prefixes is 0, 1 or 2.");
      String[] prefixes = new String[maxPossiblePrefixes];

      int foundPrefixes;
      for (foundPrefixes = 0; foundPrefixes < maxPossiblePrefixes; foundPrefixes++) {
         if (ciphertext.length == 0) {
            throw new CipherText.InvalidPrefixException(
               "After extracting " + foundPrefixes + " prefixes, the remaining ciphertext is empty. The extracted prefixes were most likely a false positive."
            );
         }

         if (ciphertext[0] != 91) {
            break;
         }

         int endIndex = Bytes.indexOf(ciphertext, (byte)93);
         if (endIndex < 0) {
            break;
         }

         byte[] prefixBytes = Arrays.copyOfRange(ciphertext, 1, endIndex);
         prefixes[foundPrefixes] = byteArrayToString(prefixBytes);
         ciphertext = Arrays.copyOfRange(ciphertext, endIndex + 1, ciphertext.length);
      }

      CipherText res = new CipherText(ciphertext);
      switch (foundPrefixes) {
         case 0:
            return res;
         case 1:
            return res.setCipherPrefix(prefixes[0]);
         case 2:
            return res.setKdfPrefix(prefixes[0]).setCipherPrefix(prefixes[1]);
         default:
            throw new IllegalArgumentException("Too many prefixes.");
      }
   }

   static CipherText createWithPlainCiphertext(byte[] ciphertext) {
      try {
         return createFromCiphertextWithPotentialPrefixes(ciphertext, 0);
      } catch (CipherText.InvalidPrefixException e) {
         throw new IllegalStateException(
            "When creating a CipherText without prefixes (maxPossiblePrefixes = 0), there should be no InvalidPrefixException => Programming Error", e
         );
      }
   }

   private CipherText(byte[] ciphertext) {
      this.ciphertext = (byte[])Preconditions.checkNotNull(ciphertext);
   }

   String getKdfPrefix() {
      return this.kdfPrefix;
   }

   CipherText setKdfPrefix(String kdfPrefix) throws CipherText.InvalidPrefixException {
      this.kdfPrefix = this.checkForInvalidSymbolsInPrefix(kdfPrefix);
      return this;
   }

   String getCipherPrefix() {
      return this.cipherPrefix;
   }

   CipherText setCipherPrefix(String cipherPrefix) throws CipherText.InvalidPrefixException {
      this.cipherPrefix = this.checkForInvalidSymbolsInPrefix(cipherPrefix);
      return this;
   }

   byte[] getCiphertextPlain() {
      return this.ciphertext;
   }

   byte[] getCiphertextWithPrefixes() {
      return this.cipherPrefix != null
         ? Arrays.concatenate(Arrays.concatenate(this.convertStringToPrefix(this.kdfPrefix), this.convertStringToPrefix(this.cipherPrefix)), this.ciphertext)
         : (byte[])this.ciphertext.clone();
   }

   private byte[] convertStringToPrefix(String prefix) {
      if (prefix != null) {
         byte[] prefixBytes = toByteArray(prefix);
         byte[] result = new byte[prefixBytes.length + 2];
         result[0] = 91;
         System.arraycopy(prefixBytes, 0, result, 1, prefixBytes.length);
         result[result.length - 1] = 93;
         return result;
      } else {
         return new byte[0];
      }
   }

   private String checkForInvalidSymbolsInPrefix(String prefix) throws CipherText.InvalidPrefixException {
      if (prefix == null) {
         return null;
      } else {
         byte[] prefixBytes = toByteArray(prefix);
         if (Bytes.indexOf(prefixBytes, (byte)91) < 0 && Bytes.indexOf(prefixBytes, (byte)93) < 0) {
            return prefix;
         } else {
            throw new CipherText.InvalidPrefixException("Prefix contains invalid character.");
         }
      }
   }

   private static byte[] toByteArray(String string) {
      return string.getBytes(StandardCharsets.ISO_8859_1);
   }

   private static String byteArrayToString(byte[] input) {
      return new String(input, StandardCharsets.ISO_8859_1);
   }

   public static final class InvalidPrefixException extends CipherException {
      private InvalidPrefixException(String msg) {
         super(msg);
      }
   }
}
