package basics.application;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import basics.unexpected.Failure;

/**
 * Constructs a property either from a file, a resource, or a Properties collection.
 * Offers a set of getters for reading the properties a types values.
 * Constructing never fails, but may will end with an empty property collection
 * if the file or resource were not found. 
 * @author: ks
 * @created Samstag, 2. Mai 2003 15:33:16
 */
public class Config
{
private static final String SUFFIX     = ".properties";
private Properties          properties = new Properties();

private static Properties loadPropertyFile(File propertyFile)
{
   Properties p = new Properties();
   try
   {
      p.load(new FileInputStream(propertyFile));
   }
   catch(Exception e)
   {
      throw new Failure(e, "Propertyfile not found: " + propertyFile.getAbsolutePath());
   }
   return p;
}

public Config()
{
   properties = new Properties();
}

public boolean isEmpty()
{
   return properties.isEmpty();
}

public Properties getProperties()
{
   return properties;
}

public Config(File file)
{
   try
   {
      properties = loadPropertyFile(file);
   }
   catch(Throwable t)
   {
      properties = new Properties();
   }
}

public Config(Properties p)
{
   properties = p == null ? new Properties() : p;
}

public Config(String resource)
{
   try
   {
      properties = loadPropertyResource(resource);
   }
   catch(Throwable t)
   {
      properties = new Properties();
   }
}

/**
 * @return keys
 */
public Iterator<Object> iterator()
{
   if(properties == null)
      return new Properties().keySet().iterator();
   return properties.keySet().iterator();
}

public void set(String key, String value)
{
   properties.setProperty(key, value);
}

public Object get(String key)
{
   return properties.getProperty(key);
}

public boolean hasKey(String key)
{
   return(properties.getProperty(key) != null);
}

public String get(String key, String defaultValue)
{
   String v = properties.getProperty(key);
   return (v == null) ? defaultValue : v.trim();
}

public int get(String key, int dv)
{
   String v = properties.getProperty(key);
   return (v == null) ? dv : atoi(key, v.trim(), dv);
}

public long get(String key, long dv)
{
   String v = properties.getProperty(key);
   return (v == null) ? dv : atol(key, v.trim(), dv);
}

public boolean get(String key, boolean dv)
{
   String v = properties.getProperty(key);
   return (v == null) ? dv : get(key, v.trim(), dv);
}

public float get(String key, float dv)
{
   String v = properties.getProperty(key);
   return (v == null) ? dv : (float)get(key, v.trim(), dv);
}

public double get(String key, double dv)
{
   String v = properties.getProperty(key);
   return (v == null) ? dv : get(key, v.trim(), dv);
}

// -----------------
private boolean get(String k, String s, boolean dv)
{
   if(s.equalsIgnoreCase("true"))
      return true;
   if(s.equalsIgnoreCase("yes"))
      return true;
   if(s.equalsIgnoreCase("1"))
      return true;
   if(s.equalsIgnoreCase("-1"))
      return true;
   if(s.equalsIgnoreCase("y"))
      return true;
   if(s.equalsIgnoreCase("j"))
      return true;
   if(s.equalsIgnoreCase("false"))
      return false;
   if(s.equalsIgnoreCase("no"))
      return false;
   if(s.equalsIgnoreCase("nein"))
      return false;
   if(s.equalsIgnoreCase("0"))
      return false;
   if(s.equalsIgnoreCase("n"))
      return false;
   if(s.equalsIgnoreCase("f"))
      return false;
   // throw new Failure("PropertyWrapper:get - Unrecognized boolean value. Key
   // '"
   // + k + "' value: '" + s);
   return dv;
}

private double get(String k, String s, double dv)
{
   int i = 0;
   int sign = 1;
   double r = 0; // integer part
   // double f = 0; // fractional part
   double p = 1; // exponent of fractional part
   int state = 0; // 0 = int part, 1 = frac part
   try
   {
      while(i < s.length() && Character.isWhitespace(s.charAt(i)))
         i++;
      if(i < s.length() && s.charAt(i) == '-')
      {
         sign = -1;
         i++;
      }
      else
         if(i < s.length() && s.charAt(i) == '+')
         {
            i++;
         }
      while(i < s.length())
      {
         char ch = s.charAt(i);
         if('0' <= ch && ch <= '9')
         {
            if(state == 0)
               r = r * 10 + ch - '0';
            else
               if(state == 1)
               {
                  p = p / 10;
                  r = r + p * (ch - '0');
               }
         }
         else
            if(ch == '.')
            {
               if(state == 0)
                  state = 1;
               else
                  return sign * r;
            }
            else
               if(ch == 'e' || ch == 'E')
               {
                  long e = (int)parseLong(s.substring(i + 1), 10);
                  return sign * r * Math.pow(10, e);
               }
               else
                  return sign * r;
         i++;
      }
      return sign * r;
   }
   catch(Exception e)
   {
      // logger.error("PropertyWrapper:get - Unrecognized numeric value. Key '"
      // + k + "' value: '" + s + "' - in property-file");
      return dv;
   }
}

private int atoi(String key, String s, int dv)
{
   return (int)atol(key, s, (int)dv);
}

/**
 * Converts a string of digits (decimal, octal or hex) to a long integer
 * @param s a string
 * @return the numeric value of the prefix of s representing a base 10 integer
 */
private long atol(String k, String s, long dv)
{
   int i = 0;
   try
   {
      while(i < s.length() && Character.isWhitespace(s.charAt(i)))
         i++;
      if(i < s.length() && s.charAt(i) == '0')
      {
         if(i + 1 < s.length() && (s.charAt(i + 1) == 'x' || s.charAt(i + 1) == 'X'))
            return parseLong(s.substring(i + 2), 16);
         else
            return parseLong(s, 8);
      }
      else
         return parseLong(s, 10);
   }
   catch(Exception e)
   {
      // logger.error("PropertyWrapper:atoi - Unrecognized float value. Key '"
      // + k + "' value: '" + s + "' - in property-file");
      return dv;
   }
}

private long parseLong(String s, int base)
{
   int i = 0;
   int sign = 1;
   long r = 0;
   while(i < s.length() && Character.isWhitespace(s.charAt(i)))
      i++;
   if(i < s.length() && s.charAt(i) == '-')
   {
      sign = -1;
      i++;
   }
   else
      if(i < s.length() && s.charAt(i) == '+')
      {
         i++;
      }
   while(i < s.length())
   {
      char ch = s.charAt(i);
      if('0' <= ch && ch < '0' + base)
         r = r * base + ch - '0';
      else
         if('A' <= ch && ch < 'A' + base - 10)
            r = r * base + ch - 'A' + 10;
         else
            if('a' <= ch && ch < 'a' + base - 10)
               r = r * base + ch - 'a' + 10;
            else
               return r * sign;
      i++;
   }
   return r * sign;
}

private static Properties loadPropertyResource(String name)
{
   ClassLoader loader = Thread.currentThread().getContextClassLoader();
   if(name.startsWith("/"))
      name = name.substring(1);
   if(name.endsWith(SUFFIX))
      name = name.substring(0, name.length() - SUFFIX.length());
   Properties result = null;
   InputStream in = null;
   try
   {
      if(loader == null)
         loader = ClassLoader.getSystemClassLoader();
      name = name.replace('/', '.');
      // throws MissingResourceException on lookup failures:
      final ResourceBundle rb = ResourceBundle.getBundle(name, Locale.getDefault(), loader);
      result = new Properties();
      for(Enumeration<String> keys = rb.getKeys(); keys.hasMoreElements();)
      {
         final String key = (String)keys.nextElement();
         final String value = rb.getString(key);
         result.put(key, value);
      }
   }
   catch(Exception e)
   {
      result = null;
   }
   finally
   {
      if(in != null)
      {
         try
         {
            in.close();
         }
         catch(Throwable ignore)
         {
         }
      }
   }
   if(result == null)
      throw new IllegalArgumentException("could not load [" + name + "] as a resource bundle");
   return result;
}

public String toString()
{
   StringBuilder s = new StringBuilder();
   s.append("{");
   Iterator<Object> iter = iterator();
   while(iter.hasNext())
   {
      Object ok = iter.next();
      String k = ok.toString();
      Object v = get(k);
      s.append(k);
      s.append(":");
      s.append(v);
      s.append(";");
   }
   s.append("}");
   return s.toString();
}

public static void unittest()
{
}
}
