/*
 * Copyright(C) 2020 The Android Open Source Project
 *
 * 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 com.android.javacard.keymaster;

import com.android.javacard.seprovider.KMException;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

/**
 * Represents 8 bit, 16 bit, 32 bit and 64 bit unsigned integer. It corresponds to CBOR uint type.
 * struct{byte INTEGER_TYPE; short length; 4 or 8 bytes of value}
 */
public class KMInteger extends KMType {

  public static final byte UINT_32 = 4;
  public static final byte UINT_64 = 8;
  private static KMInteger prototype;

  protected KMInteger() {}

  private static KMInteger proto(short ptr) {
    if (prototype == null) {
      prototype = new KMInteger();
    }
    KMType.instanceTable[KM_INTEGER_OFFSET] = ptr;
    return prototype;
  }

  // | TYPE(1) | LEN(2) | DATA(4 / 8) |
  public static short exp() {
    return KMType.exp(INTEGER_TYPE);
  }

  // return an empty integer instance
  public static short instance(short length) {
    if ((length <= 0) || (length > 8)) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    if (length > 4) {
      length = UINT_64;
    } else {
      length = UINT_32;
    }
    return KMType.instance(INTEGER_TYPE, length);
  }

  public static short instance(byte[] num, short srcOff, short length) {
    if (length > 8) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    if (length == 1) {
      return uint_8(num[srcOff]);
    } else if (length == 2) {
      return uint_16(Util.getShort(num, srcOff));
    } else if (length == 4) {
      return uint_32(num, srcOff);
    } else {
      return uint_64(num, srcOff);
    }
  }

  public static KMInteger cast(short ptr) {
    byte[] heap = repository.getHeap();
    if (heap[ptr] != INTEGER_TYPE) {
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) {
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    return proto(ptr);
  }

  // create integer and copy byte value
  public static short uint_8(byte num) {
    short ptr = instance(UINT_32);
    heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num;
    return ptr;
  }

  // create integer and copy short value
  public static short uint_16(short num) {
    short ptr = instance(UINT_32);
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num);
    return ptr;
  }

  // create integer and copy integer value
  public static short uint_32(byte[] num, short offset) {
    short ptr = instance(UINT_32);
    Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32);
    return ptr;
  }

  // create integer and copy integer value
  public static short uint_64(byte[] num, short offset) {
    short ptr = instance(UINT_64);
    Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64);
    return ptr;
  }

  public static short compare(short num1, short num2) {
    short num1Buf = repository.alloc((short) 8);
    short num2Buf = repository.alloc((short) 8);
    Util.arrayFillNonAtomic(repository.getHeap(), num1Buf, (short) 8, (byte) 0);
    Util.arrayFillNonAtomic(repository.getHeap(), num2Buf, (short) 8, (byte) 0);
    short len = KMInteger.cast(num1).length();
    KMInteger.cast(num1).getValue(repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len);
    len = KMInteger.cast(num2).length();
    KMInteger.cast(num2).getValue(repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len);
    return KMInteger.unsignedByteArrayCompare(
        repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, (short) 8);
  }

  public static byte unsignedByteArrayCompare(
      byte[] a1, short offset1, byte[] a2, short offset2, short length) {
    byte count = (byte) 0;
    short val1 = (short) 0;
    short val2 = (short) 0;

    for (; count < length; count++) {
      val1 = (short) (a1[(short) (count + offset1)] & 0x00FF);
      val2 = (short) (a2[(short) (count + offset2)] & 0x00FF);

      if (val1 < val2) {
        return -1;
      }
      if (val1 > val2) {
        return 1;
      }
    }
    return 0;
  }

  // Get the length of the integer
  public short length() {
    return Util.getShort(heap, (short) (getBaseOffset() + 1));
  }

  // Get the buffer pointer in which blob is contained.
  public byte[] getBuffer() {
    return heap;
  }

  // Get the start of value
  public short getStartOff() {
    return (short) (getBaseOffset() + TLV_HEADER_SIZE);
  }

  public void getValue(byte[] dest, short destOff, short length) {
    if (length < length()) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    if (length > length()) {
      length = length();
      destOff += length;
    }
    Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length);
  }

  public short value(byte[] dest, short destOff) {
    Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length());
    return length();
  }

  public short toLittleEndian(byte[] dest, short destOff) {
    short index = (short) (length() - 1);
    while (index >= 0) {
      dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)];
      index--;
    }
    return length();
  }

  public short getShort() {
    return Util.getShort(heap, (short) (getStartOff() + 2));
  }

  public short getSignificantShort() {
    return Util.getShort(heap, getStartOff());
  }

  public byte getByte() {
    return heap[(short) (getStartOff() + 3)];
  }

  public boolean isZero() {
    if (getShort() == 0 && getSignificantShort() == 0) {
      return true;
    }
    return false;
  }

  protected short getBaseOffset() {
    return instanceTable[KM_INTEGER_OFFSET];
  }
}
