package net.sf.jasperreports.engine.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRPropertiesHolder;
import net.sf.jasperreports.engine.JRPropertiesMap;
import net.sf.jasperreports.engine.JRRuntimeException;

public class JRProperties {
  protected static final String DEFAULT_PROPERTIES_FILE = "jasperreports.properties";
  
  public static final String PROPERTY_PREFIX = "net.sf.jasperreports.";
  
  public static final String PROPERTIES_FILE = "net.sf.jasperreports.properties";
  
  public static final String COMPILER_CLASS = "net.sf.jasperreports.compiler.class";
  
  public static final String COMPILER_XML_VALIDATION = "net.sf.jasperreports.compiler.xml.validation";
  
  public static final String COMPILER_KEEP_JAVA_FILE = "net.sf.jasperreports.compiler.keep.java.file";
  
  public static final String COMPILER_TEMP_DIR = "net.sf.jasperreports.compiler.temp.dir";
  
  public static final String COMPILER_CLASSPATH = "net.sf.jasperreports.compiler.classpath";
  
  public static final String EXPORT_XML_VALIDATION = "net.sf.jasperreports.export.xml.validation";
  
  public static final String PDF_FONT_FILES_PREFIX = "net.sf.jasperreports.export.pdf.font.";
  
  public static final String PDF_FONT_DIRS_PREFIX = "net.sf.jasperreports.export.pdf.fontdir.";
  
  public static final String QUERY_EXECUTER_FACTORY_PREFIX = "net.sf.jasperreports.query.executer.factory.";
  
  public static final String SUBREPORT_RUNNER_FACTORY = "net.sf.jasperreports.subreport.runner.factory";
  
  public static final String PDF_FORCE_LINEBREAK_POLICY = "net.sf.jasperreports.export.pdf.force.linebreak.policy";
  
  protected static Properties props;
  
  protected static Properties savedProps;
  
  static {
    initProperties();
  }
  
  protected static void initProperties() {
    try {
      Properties defaults = getDefaults();
      String propFile = getSystemProperty("net.sf.jasperreports.properties");
      if (propFile == null) {
        props = loadProperties("jasperreports.properties", defaults);
        if (props == null)
          props = new Properties(defaults); 
      } else {
        props = loadProperties(propFile, defaults);
        if (props == null)
          throw new JRRuntimeException("Could not load properties file \"" + propFile + "\""); 
      } 
      loadSystemProperties();
    } catch (JRException e) {
      throw new JRRuntimeException("Error loading the properties", e);
    } 
  }
  
  protected static void loadSystemProperties() {
    loadSystemProperty("jasper.reports.compiler.class", "net.sf.jasperreports.compiler.class");
    loadSystemProperty("jasper.reports.compile.xml.validation", "net.sf.jasperreports.compiler.xml.validation");
    loadSystemProperty("jasper.reports.export.xml.validation", "net.sf.jasperreports.export.xml.validation");
    loadSystemProperty("jasper.reports.compile.keep.java.file", "net.sf.jasperreports.compiler.keep.java.file");
    loadSystemProperty("jasper.reports.compile.temp", "net.sf.jasperreports.compiler.temp.dir");
    loadSystemProperty("jasper.reports.compile.class.path", "net.sf.jasperreports.compiler.classpath");
  }
  
  protected static Properties getDefaults() throws JRException {
    Properties defaults = new Properties();
    InputStream is = JRProperties.class.getResourceAsStream("/default.jasperreports.properties");
    if (is == null)
      throw new JRException("Default properties file not found."); 
    try {
      defaults.load(is);
    } catch (IOException e) {
      throw new JRException("Failed to load default properties.", e);
    } finally {
      try {
        is.close();
      } catch (IOException e) {}
    } 
    String userDir = getSystemProperty("user.dir");
    if (userDir != null)
      defaults.setProperty("net.sf.jasperreports.compiler.temp.dir", userDir); 
    String classPath = getSystemProperty("java.class.path");
    if (classPath != null)
      defaults.setProperty("net.sf.jasperreports.compiler.classpath", classPath); 
    return defaults;
  }
  
  protected static String getSystemProperty(String propertyName) {
    try {
      return System.getProperty(propertyName);
    } catch (SecurityException e) {
      return null;
    } 
  }
  
  protected static void loadSystemProperty(String sysKey, String propKey) {
    String val = getSystemProperty(sysKey);
    if (val != null)
      props.setProperty(propKey, val); 
  }
  
  public static Properties loadProperties(String name, Properties defaults) throws JRException {
    Properties properties = null;
    InputStream is = null;
    try {
      is = JRLoader.getLocationInputStream(name);
    } catch (SecurityException e) {}
    if (is != null) {
      properties = new Properties(defaults);
      try {
        properties.load(is);
      } catch (IOException e) {
        throw new JRException("Failed to load properties file \"" + name + "\"", e);
      } finally {
        try {
          is.close();
        } catch (IOException e) {}
      } 
    } 
    return properties;
  }
  
  public static String getProperty(String key) {
    return props.getProperty(key);
  }
  
  public static boolean getBooleanProperty(String key) {
    return asBoolean(props.getProperty(key));
  }
  
  public static int getIntegerProperty(String key) {
    return asInteger(props.getProperty(key));
  }
  
  public static boolean asBoolean(String value) {
    return Boolean.valueOf(value).booleanValue();
  }
  
  public static int asInteger(String value) {
    return Integer.parseInt(value);
  }
  
  public static void setProperty(String key, String value) {
    props.setProperty(key, value);
  }
  
  public static void setProperty(String key, boolean value) {
    props.setProperty(key, String.valueOf(value));
  }
  
  public static void backupProperties() {
    savedProps = (Properties)props.clone();
  }
  
  public static void restoreProperties() {
    if (savedProps != null)
      try {
        props.clear();
        props.putAll(savedProps);
      } finally {
        savedProps = null;
      }  
  }
  
  public static class PropertySuffix {
    protected final String key;
    
    protected final String suffix;
    
    protected final String value;
    
    public PropertySuffix(String key, String suffix, String value) {
      this.key = key;
      this.suffix = suffix;
      this.value = value;
    }
    
    public String getKey() {
      return this.key;
    }
    
    public String getSuffix() {
      return this.suffix;
    }
    
    public String getValue() {
      return this.value;
    }
  }
  
  public static List getProperties(String prefix) {
    int prefixLength = prefix.length();
    List values = new ArrayList();
    for (Enumeration names = props.propertyNames(); names.hasMoreElements(); ) {
      String name = (String)names.nextElement();
      if (name.startsWith(prefix)) {
        String suffix = name.substring(prefixLength);
        String value = props.getProperty(name);
        values.add(new PropertySuffix(name, suffix, value));
      } 
    } 
    return values;
  }
  
  public static List getProperties(JRPropertiesHolder propertiesHolder, String prefix) {
    return getProperties(getOwnProperties(propertiesHolder), prefix);
  }
  
  public static List getProperties(JRPropertiesMap propertiesMap, String prefix) {
    int prefixLength = prefix.length();
    List values = new ArrayList();
    if (propertiesMap != null) {
      String[] propertyNames = propertiesMap.getPropertyNames();
      for (int i = 0; i < propertyNames.length; i++) {
        String name = propertyNames[i];
        if (name.startsWith(prefix)) {
          String suffix = name.substring(prefixLength);
          String value = propertiesMap.getProperty(name);
          values.add(new PropertySuffix(name, suffix, value));
        } 
      } 
    } 
    return values;
  }
  
  public static String getProperty(JRPropertiesHolder propertiesHolder, String key) {
    String value = null;
    while (propertiesHolder != null && value == null) {
      if (propertiesHolder.hasProperties())
        value = propertiesHolder.getPropertiesMap().getProperty(key); 
      propertiesHolder = propertiesHolder.getParentProperties();
    } 
    if (value == null)
      value = props.getProperty(key); 
    return value;
  }
  
  public static String getProperty(JRPropertiesMap propertiesMap, String key) {
    String value = null;
    if (propertiesMap != null)
      value = propertiesMap.getProperty(key); 
    if (value == null)
      value = props.getProperty(key); 
    return value;
  }
  
  public static boolean getBooleanProperty(JRPropertiesHolder propertiesHolder, String key, boolean defaultValue) {
    String value = getProperty(propertiesHolder, key);
    return (value == null) ? defaultValue : asBoolean(value);
  }
  
  public static boolean getBooleanProperty(JRPropertiesMap propertiesMap, String key, boolean defaultValue) {
    String value = getProperty(propertiesMap, key);
    return (value == null) ? defaultValue : asBoolean(value);
  }
  
  public static int getIntegerProperty(JRPropertiesHolder propertiesHolder, String key, int defaultValue) {
    String value = getProperty(propertiesHolder, key);
    return (value == null) ? defaultValue : asInteger(value);
  }
  
  public static int getIntegerProperty(JRPropertiesMap propertiesMap, String key, int defaultValue) {
    String value = getProperty(propertiesMap, key);
    return (value == null) ? defaultValue : asInteger(value);
  }
  
  public static int getIntegerProperty(String key, int defaultValue) {
    String value = getProperty(key);
    return (value == null) ? defaultValue : asInteger(value);
  }
  
  public static long asLong(String value) {
    return Long.parseLong(value);
  }
  
  public static long getLongProperty(String key) {
    return asLong(props.getProperty(key));
  }
  
  public static long getLongProperty(JRPropertiesMap propertiesMap, String key, int defaultValue) {
    String value = getProperty(propertiesMap, key);
    return (value == null) ? defaultValue : asLong(value);
  }
  
  protected static JRPropertiesMap getOwnProperties(JRPropertiesHolder propertiesHolder) {
    return propertiesHolder.hasProperties() ? propertiesHolder.getPropertiesMap() : null;
  }
  
  public static void transferProperties(JRPropertiesHolder source, JRPropertiesHolder destination, String tranferPropertiesPrefix) {
    if (!source.hasProperties())
      return; 
    transfer(source.getPropertiesMap(), destination, tranferPropertiesPrefix);
  }
  
  public static void transferProperties(JRPropertiesMap source, JRPropertiesHolder destination, String tranferPropertiesPrefix) {
    if (source == null || !source.hasProperties())
      return; 
    transfer(source, destination, tranferPropertiesPrefix);
  }
  
  protected static void transfer(JRPropertiesMap source, JRPropertiesHolder destination, String tranferPropertiesPrefix) {
    List transferPrefixProps = getProperties(tranferPropertiesPrefix);
    for (Iterator prefixIt = transferPrefixProps.iterator(); prefixIt.hasNext(); ) {
      PropertySuffix transferPrefixProp = prefixIt.next();
      String transferPrefix = transferPrefixProp.getValue();
      if (transferPrefix != null && transferPrefix.length() > 0) {
        List transferProps = getProperties(source, transferPrefix);
        for (Iterator propIt = transferProps.iterator(); propIt.hasNext(); ) {
          PropertySuffix property = propIt.next();
          String value = property.getValue();
          destination.getPropertiesMap().setProperty(property.getKey(), value);
        } 
      } 
    } 
  }
}
