/*
 * Copyright (C) 2013 - 2022 ScalAgent Distributed Technologies
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 * 
 * Initial developer(s): ScalAgent Distributed Technologies
 * Contributor(s): 
 */
package fr.dyade.aaa.common.encoding;

import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map.Entry;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.Debug;

import java.util.Properties;
import java.util.Set;

import fr.dyade.aaa.common.Configuration;

public final class EncodableHelper {
  static Logger logger = Debug.getLogger(EncodableHelper.class.getName());
  
  // ############################################################
  // Fix charset used to encode/decode String objects.
  
  public final static String ENCODING_CHARSET_PROPERTY = "fr.dyade.aaa.common.encoding.charset";
  public final static String ENCODING_USE_JVM_CHARSET_PROPERTY = "fr.dyade.aaa.common.encoding.useJVMcharset";
  public final static String ENCODING_CHARSET_DFLT = "UTF-8";
  
  /** Default charset used to encode/decode String. */
  final static Charset charset;
  /** Attributes allowing to compute encoded String size. */
  private final static boolean multibyte;
  private final static boolean utf8;
  
  static {
    if (Configuration.getBoolean(ENCODING_USE_JVM_CHARSET_PROPERTY)) {
      charset = Charset.defaultCharset();
    } else {
      charset = Charset.forName(Configuration.getProperty(ENCODING_CHARSET_PROPERTY, ENCODING_CHARSET_DFLT));
    }
    multibyte = charset.newEncoder().maxBytesPerChar() > 1.0f;
    utf8 = "UTF-8".equals(charset.name());
    
    logger.log(BasicLevel.INFO,
               "Encodable configuration: " + charset.displayName() + ", multibyte=" + multibyte + ", utf8=" + utf8);
  }
  
  //############################################################
  // Computing of size for String and byte array.

  /**
   * Returns the size of an encoded string.
   * Be careful, String are now encoded as UTF-8.
   * 
   * @param s the string to encode
   * @return the size of the encoded string
   */
  public static final int getStringEncodedSize(String s) {
	if (! multibyte)
	  return Encodable.INT_ENCODED_SIZE + s.length();
	else if (utf8)
	  return Encodable.INT_ENCODED_SIZE + Utf8.encodedLength(s);
	else
	  return Encodable.INT_ENCODED_SIZE + s.getBytes().length;
  }
  
  /**
   * Returns the size of an encoded string which value may be null.
   * Be careful, String are now encoded as UTF-8.
   * 
   * @param s the string to encode
   * @return the size of the encoded string
   */
  public static final int getNullableStringEncodedSize(String s) {
    int res = Encodable.BYTE_ENCODED_SIZE;
    if (s != null) {
      res += getStringEncodedSize(s);
    }
    return res;
  }

  public static final int getByteArrayEncodedSize(byte[] byteArray) {
    return Encodable.INT_ENCODED_SIZE + byteArray.length;
  }
  
  public static final int getNullableByteArrayEncodedSize(byte[] byteArray) {
    int res = Encodable.BYTE_ENCODED_SIZE;
    if (byteArray != null) {
      res += Encodable.INT_ENCODED_SIZE + byteArray.length;
    }
    return res;
  }
  
  public static final int getNullableByteArrayEncodedSize(byte[] byteArray, int length) {
    int res = Encodable.BYTE_ENCODED_SIZE;
    if (byteArray != null) {
      res += Encodable.INT_ENCODED_SIZE + length;
    }
    return res;
  }
  
  // ################################################################################
  // Helper to encode java.util.Properties.
  // Used in Destination and UserAgent for interceptors.
  // ################################################################################
  
  public static int getEncodedSize(Properties properties) throws Exception {
    int res = Encodable.INT_ENCODED_SIZE;
    Set<Entry<Object, Object>> entries = properties.entrySet();
    Iterator<Entry<Object, Object>> iterator = entries.iterator();
    while (iterator.hasNext()) {
      Entry<Object, Object> entry = iterator.next();
      res += getStringEncodedSize((String) entry.getKey());
      res += getStringEncodedSize((String) entry.getValue());
    }
    return res;
  }
  
  public static void encodeProperties(Properties properties, Encoder encoder) throws Exception {
    encoder.encodeUnsignedInt(properties.size());
    Set<Entry<Object, Object>> entries = properties.entrySet();
    Iterator<Entry<Object, Object>> iterator = entries.iterator();
    while (iterator.hasNext()) {
      Entry<Object, Object> entry = iterator.next();
      encoder.encodeString((String) entry.getKey());
      encoder.encodeString((String) entry.getValue());
    }
  }

  public static Properties decodeProperties(Decoder decoder) throws Exception {
    Properties properties = new Properties();
    int size = decoder.decodeUnsignedInt();
    for (int i = 0; i < size; i++) {
      String key = decoder.decodeString();
      String value = decoder.decodeString();
      properties.put(key, value);
    }
    return properties;
  }
}
