/* Copyright  (c) 2002 Graz University of Technology. All rights reserved.
 *
 * Redistribution and use in  source and binary forms, with or without 
 * modification, are permitted  provided that the following conditions are met:
 *
 * 1. Redistributions of  source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in  binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  
 * 3. The end-user documentation included with the redistribution, if any, must
 *    include the following acknowledgment:
 * 
 *    "This product includes software developed by IAIK of Graz University of
 *     Technology."
 * 
 *    Alternately, this acknowledgment may appear in the software itself, if 
 *    and wherever such third-party acknowledgments normally appear.
 *  
 * 4. The names "Graz University of Technology" and "IAIK of Graz University of
 *    Technology" must not be used to endorse or promote products derived from 
 *    this software without prior written permission.
 *  
 * 5. Products derived from this software may not be called 
 *    "IAIK PKCS Wrapper", nor may "IAIK" appear in their name, without prior 
 *    written permission of Graz University of Technology.
 *  
 *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 *  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 *  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY  OF SUCH DAMAGE.
 */

package iaik.pkcs.pkcs11;

import iaik.pkcs.pkcs11.wrapper.CK_ATTRIBUTE;
import iaik.pkcs.pkcs11.wrapper.CK_DATE;

import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Vector;



/**
 * A class consisting of static methods only which provide certain static
 * piecec of code that are used frequently in this project.
 *
 * @author <a href="mailto:Karl.Scheibelhofer@iaik.at"> Karl Scheibelhofer </a>
 * @version 1.0
 * @invariants
 */
public class Util {

  /**
   * Parse a time character array as defined in PKCS#11 and return is as a
   * Date object.
   *
   * @param timeChars A time encoded as character array as specified in PKCS#11.
   * @return A Date object set to the time indicated in the given char-array.
   *         null, if the given char array is null or the format is wrong.
   * @preconditions
   * @postconditions
   */
  public static Date parseTime(char[] timeChars) {
    Date time = null;

    if ((timeChars != null) && timeChars.length > 2){
      String timeString = new String(timeChars, 0, timeChars.length - 2);
      try {
        time = new SimpleDateFormat("yyyyMMddhhmmss").parse(timeString);
      } catch (ParseException ex) { /* nothing else to be done */ }
    }

    return time ;
  }

  /**
   * Convert the given CK_DATE object to a Date object.
   *
   * @param ckDate The object providing the date information.
   * @return The new Date object or null, if the given ckDate is null.
   * @preconditions
   * @postconditions
   */
  public static Date convertToDate(CK_DATE ckDate) {
    Date date = null;

    if (ckDate != null) {
      int year = Integer.parseInt(new String(ckDate.year));
      int month = Integer.parseInt(new String(ckDate.month));
      int day = Integer.parseInt(new String(ckDate.day));
      Calendar calendar = new GregorianCalendar(); //poor performance, consider alternatives
      calendar.set(year, Calendar.JANUARY + (month - 1), day); // calendar starts months with 0
      date = calendar.getTime();
    }

    return date ;
  }

  /**
   * Convert the given Date object to a CK_DATE object.
   *
   * @param date The object providing the date information.
   * @return The new CK_DATE object or null, if the given date is null.
   * @preconditions
   * @postconditions
   */
  public static CK_DATE convertToCkDate(Date date) {
    CK_DATE ckDate = null;

    if (date != null) {
      Calendar calendar = new GregorianCalendar(); //poor memory/performance behavior, consider alternatives
      calendar.setTime(date);
      int year = calendar.get(Calendar.YEAR);
      int month = calendar.get(Calendar.MONTH) + 1; // month counting starts with zero
      int day = calendar.get(Calendar.DAY_OF_MONTH);
      ckDate = new CK_DATE();
      ckDate.year = toCharArray(year, 4);
      ckDate.month = toCharArray(month, 2);
      ckDate.day = toCharArray(day, 2);
    }

    return ckDate ;
  }

  /**
   * Converts the given number into a char-array. If the length of the array
   * is shorter than the required exact length, the array is padded with leading
   * '0' chars. If the array is longer than the wanted length the most
   * significant digits are cut off until the array has the exact length.
   *
   * @param number The number to convert to a char array.
   * @param exactArrayLength The exact length of the returned array.
   * @return The numebr as char array, one char for each decimal digit.
   * @preconditions (exactArrayLength >= 0)
   * @postconditions (result <> null)
   *                 and (result.length == exactArrayLength)
   */
  public static char[] toCharArray(int number, int exactArrayLength) {
    char[] charArray = null;

    String numberString = Integer.toString(number);
    char[] numberChars = numberString.toCharArray();

    if (numberChars.length > exactArrayLength) {
      // cut off digits beginning at most significant digit
      charArray = new char[exactArrayLength];
      for (int i = 0; i < charArray.length; i++) {
        charArray[i] = numberChars[i];
      }
    } else if (numberChars.length < exactArrayLength) {
      // pad with '0' leading chars
      charArray = new char[exactArrayLength];
      int offset = exactArrayLength - numberChars.length;
      for (int i = 0; i < charArray.length; i++) {
        charArray[i] = (i < offset) ? '0' : numberChars[i - offset];
      }
    } else {
      charArray = numberChars;
    }

    return charArray ;
  }

  /**
   * Converts the given string to a char-array of exactly the given length.
   * If the given string is short than the wanted length, then the array is
   * padded with trailing padding chars. If the string is longer, the last
   * character are cut off that the string has the wanted size.
   *
   * @param string The string to convert.
   * @param exactArrayLength The length of the retirned char-array.
   * @param paddingChar The character to use for padding, if necessary.
   * @return The string as char array, padded or cut off, if necessary.
   *         The array will have length exactArrayLength. null, if the
   *         given string is null.
   * @preconditions (exactArrayLength >= 0)
   * @postconditions (result == null)
   *                 or (result <> null)
   *                    and (result.length == exactArrayLength)
   */
  public static char[] toPaddedCharArray(String string, int exactArrayLength, char paddingChar) {
    char[] charArray = null;

    if (string != null) {
      int stringLength = string.length();
      charArray = new char[exactArrayLength];
      string.getChars(0, Math.min(stringLength, exactArrayLength),  charArray, 0);
      for (int i = stringLength; i < charArray.length; i++) { // fill the rest of the array with padding char
        charArray[i] = paddingChar;
      }
    }

    return charArray ;
  }

  /**
   * Convert a BigInteger to a byte-array, but treat the byte-array given from
   * the BigInteger as unsigned and removing any leading zero bytes; e.g. a
   * 1024 bit integer with its highest bit set will result in an 128 byte
   * array.
   *
   * @param bigInteger The BigInteger to convert.
   * @return The byte-array representation of the BigInterger without
   *         signum-bit. null, if the BigInteger is null.
   * @preconditions
   * @postconditions
   */
  public static byte[] unsignedBigIntergerToByteArray(BigInteger bigInteger) {
    if (bigInteger == null) {
      return null ;
    }
    byte[] integerBytes = bigInteger.toByteArray();
    byte[] unsignedIntegerBytes;
    if((integerBytes.length > 0) && (integerBytes[0] == 0x00)) {
      unsignedIntegerBytes = new byte[integerBytes.length -1];
      for (int i = 0; i < unsignedIntegerBytes.length; i++) {
        unsignedIntegerBytes[i] = integerBytes[i+1];
      }
    } else {
      unsignedIntegerBytes = integerBytes;
    }

    return unsignedIntegerBytes ;
  }

  /**
   * Converts the given vector into an array of CK_ATTRIBUTE elements. Elements
   * not of type CK_ATTRIBUTE will not be present in the resulting array and
   * be set to null.
   *
   * @param attributes The vector which contains the attributes.
   * @return The array of the attributes.
   * @preconditions
   * @postconditions (attributes <> null) implies (result.length == attributes.size())
   */
  public static CK_ATTRIBUTE[] convertAttributesVectorToArray(Vector attributes) {
    if (attributes == null) {
      return null ;
    }
    int numberOfAttributes = attributes.size();
    CK_ATTRIBUTE[] attributeArray = new CK_ATTRIBUTE[numberOfAttributes];
    Object currentVectorEntry;

    for (int i=0; i < numberOfAttributes; i++) {
      currentVectorEntry = attributes.elementAt(i);
      attributeArray[i] = (currentVectorEntry instanceof CK_ATTRIBUTE)
                          ? (CK_ATTRIBUTE) currentVectorEntry
                          : null;
    }

    return attributeArray ;
  }

}
