package fr.unice.securite;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;

public class UtilSecurite {
  /**
   * Codages supportés pour passer de byte[] à String.
   * @author richard
   *
   */
  public enum Codage { BASE64, HEX};
  /**
   * Nom de l'algorithme utilisé par défaut pour la fonction de hachage ("message digest").
   */
  private static final String NOM_MESSAGE_DIGEST_DEFAUT= "SHA-256";
  
  /**
   * Nom du codage par défaut pour passer de byte[] à String
   */
  private static final Codage CODAGE_DEFAUT = Codage.BASE64;
  
  /**
   * Nom du charset par défaut.
   */
  private static final String NOM_CHARSET_DEFAUT = "UTF-8";
  
  /**
   * Renvoie le Message Digest d'un tableau d'octets.
   */
  public static byte[] crypter(char[] source, MessageDigest messageDigest) {
    try {
      return messageDigest.digest(charToByte(source));
    } catch (CharacterCodingException e) {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * Renvoie le Message Digest d'un tableau d'octets.
   */
  public static byte[] crypter(char[] source, String nomMessageDigest) {
    try {
      MessageDigest md = MessageDigest.getInstance(nomMessageDigest);
      return crypter(source, md);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      return null;
    }
  }
  
  /**
   * Renvoie une String qui est le Message Digest d'un tableau d'octets,
   * codé avec le codage (Base64, Hex,...).
   * @param source
   * @param nomMessageDigest
   * @param codage
   * @return
   */
  public static String crypterCoder(char[] source, String nomMessageDigest, Codage codage) {
      return coder(crypter(source, nomMessageDigest), codage);
  }
  
  /**
   * Renvoie une String cryptée avec le Message Digest par défaut (SHA-256)
   * codé avec le codage par défaut (Base 64). 
   * @param source
   * @return
   */
  public static String crypterCoder(char[] source) {
    return crypterCoder(source, NOM_MESSAGE_DIGEST_DEFAUT, CODAGE_DEFAUT);
  }

  /**
   * Renvoie le Message Digest d'un tableau d'octets.
   */
  public static byte[] crypter(char[] source) {
    return crypter(source, NOM_MESSAGE_DIGEST_DEFAUT);
  }

  /**
   * Renvoie le md5 d'un tableau d'octets.
   * 
   * @return 
   */
  public static byte[] md5(char[] source) {
    return crypter(source, "MD5");
  }

  /**
   * Renvoie le SHA-256 d'un tableau d'octets.
   * 
   * @param source le tableau de char qui est hashé avec SHA-256.
   * @return tableau de 32 octets (256 bits) qui est le résultat
   * du hash SHA-256.
   */
  public static byte[] sha256(char[] source) {
    return crypter(source, "SHA-256");
  }

  /**
   * Transforme un tableau de char en un tableau d'octets
   * en utilisant le charset passé en paramètre.
   * @throws CharacterCodingException 
   */
  private static byte[] charToByte(char[] source, Charset cs) 
      throws CharacterCodingException {
    CharsetEncoder encoder = cs.newEncoder();
    ByteBuffer bbuf = encoder.encode(CharBuffer.wrap(source));
    //    try {
    //      bbuf = encoder.encode(CharBuffer.wrap(source));
    //    } catch (CharacterCodingException e) {
    //      e.printStackTrace();
    //    }
    return bbuf.array();
  }
  
  private static byte[] charToByte2(char[] source, Charset cs) 
      throws CharacterCodingException {
    // Transformer char[] en String
    String s = new String(source);
    // Et utiliser getBytes pour avoir les octets
    return s.getBytes(cs);
  }

  /**
   * Transforme un tableau de char en un tableau d'octets
   * en utilisant le charset dont le nom est passé en paramètre.
   * @throws CharacterCodingException 
   */
  private static byte[] charToByte(char[] source, String nomCharset) 
      throws CharacterCodingException {
    return charToByte(source, Charset.forName(nomCharset));
  }

  /**
   * Transforme un tableau de char en un tableau d'octets
   * en utilisant le charset par défaut (UTF-8).
   * @throws CharacterCodingException 
   */
  private static byte[] charToByte(char[] source) 
      throws CharacterCodingException {
    return charToByte(source, NOM_CHARSET_DEFAUT);
  }
  
  /**
   * Code un tableau d'octets en String.
   * 
   * Remarque : Java 8 introduit une nouvelle classe java.util.Base64
   * 
   * @param t le tableau à coder
   * @param codage le codage utilisé (Base64 ou Hex)
   * @return
   */
  public static String coder(byte[] t, Codage codage) {
    switch (codage) {
    case BASE64:
      return DatatypeConverter.printBase64Binary(t);
    case HEX:
      return DatatypeConverter.printHexBinary(t);
    default:
      // Ne devrait jamais arriver, sauf en cas d'ajout de valeur
      // en oubliant d'ajouter ici un traitement.
      throw new IllegalArgumentException("Codage pas supporté actuellement.");
    }
  }
  
  public static String coderHex(byte[] t) {
    return coder(t, Codage.HEX);
  }
  
  public static String coderBase64(byte[] t) {
    return coder(t, Codage.BASE64);
  }
  
  public static byte[] decoder(String s, Codage codage) {
    switch (codage) {
    case BASE64:
      return DatatypeConverter.parseBase64Binary(s);
    case HEX:
      return DatatypeConverter.parseHexBinary(s);
    default:
      // Ne devrait jamais arriver, sauf en cas d'ajout de valeur
      // en oubliant d'ajouter ici un traitement.
      throw new IllegalArgumentException("Codage pas supporté actuellement.");
    }
  }
  
  public static byte[] decoderHex(String s) {
    return decoder(s, Codage.HEX);
  }
  
  public static byte[] decoderBase64(String s) {
    return decoder(s, Codage.BASE64);
  }
}
