package at.gv.egiz.pdfas.utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;

import at.gv.egiz.pdfas.api.commons.Constants;
import at.gv.egiz.pdfas.api.exceptions.ConfigUtilsException;
import at.knowcenter.wag.egov.egiz.cfg.SettingsReader;
import at.knowcenter.wag.egov.egiz.pdf.Utils;

/**
 * @author <a href="mailto:thomas.knall@egiz.gv.at">Thomas Knall</a>
 */
public final class ConfigUtils {
   
   private ConfigUtils() {
   }
   
   /**
    * The log.
    */
   private static final Log logger_ = LogFactory.getLog(ConfigUtils.class);
   
   /**
    * Deploys the default configuration with apache commons vfs.
    * 
    * @param destination The destination folder.
    * @param overwriteExisting If set <code>true</code> an already existing configuration is overwritten. If <code>false</code> nothing is being copied if the destination folder already exists.
    * @return <code>true</code> if the configuration has been deployed, <code>false</code> if not.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    */
   /*
   private static boolean deployWithCommonsVFS(String destination, boolean overwriteExisting) throws ConfigUtilsException {
      try {
         FileSystemManager fsManager = VFS.getManager();
         FileObject defaultConfigurationFile = fsManager.resolveFile("res:DefaultConfiguration");
         FileObject destinationFile = fsManager.resolveFile(destination);

         if (destinationFile.exists() && !overwriteExisting) {
            return false;
         }
         
         destinationFile.copyFrom(defaultConfigurationFile, new AllFileSelector());
         return true;
      } catch (FileSystemException e) {
         throw new ConfigUtilsException(e);
      }
   }
   */

   
   /**
    * Deploys the default configuration from an included zip file.
    * 
    * @param destination The destination folder.
    * @param overwriteExisting If set <code>true</code> an already existing configuration is overwritten. If <code>false</code> nothing is being copied if the destination folder already exists.
    * @return <code>true</code> if the configuration has been deployed, <code>false</code> if not.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    */
   private static boolean deployFromZIP(String destination, boolean overwriteExisting) throws ConfigUtilsException {
      try {
         if (!overwriteExisting) {
            if (configurationAlreadyExists(destination)) {
               logger_.debug("There is at least one file or folder that would be overwritten at destination path \"" + destination + "\". Skipping extraction.");
               return false;
            }
         }
         InputStream in = ConfigUtils.class.getClassLoader().getResourceAsStream(Constants.DEFAULT_CONFIGURATION_ZIP_RESOURCE);
         if (in == null) {
            throw new ConfigUtilsException("Unable to find default configuration resource \"" + Constants.DEFAULT_CONFIGURATION_ZIP_RESOURCE + "\".");
         }
         return deployFromZIP(in, destination, overwriteExisting);
      } catch (IOException e) {
         throw new ConfigUtilsException(e);
      }
   }
   
   /**
    * Deploys the contents of a ZIP file to a certain location.
    * 
    * @param inputStream The inputStream of a ZIP container.
    * @param destination The destination folder.
    * @param overwriteExisting If set <code>true</code> an already existing configuration is overwritten. If <code>false</code> nothing is being copied if the destination folder already exists.
    * @return <code>true</code> if the configuration has been deployed, <code>false</code> if not.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    */
   public static boolean deployFromZIP(InputStream inputStream, String destination, boolean overwriteExisting) throws ConfigUtilsException {
      try {
         if (!overwriteExisting) {
            if (configurationAlreadyExists(destination)) {
               logger_.debug("There is at least one file or folder that would be overwritten at destination path \"" + destination + "\". Skipping extraction.");
               return false;
            }
         }
         if (inputStream == null) {
            throw new ConfigUtilsException("Unable to deploy ZIP file. InputStream is null.");
         }
         ZipInputStream zis = new ZipInputStream(inputStream);
         ZipEntry ze;
         File destinationFolder = new File(destination);
         destinationFolder.mkdirs();
         logger_.debug("Extracting ZIP contents to folder \"" + destinationFolder.getCanonicalPath() + "\".");
         while ((ze = zis.getNextEntry()) != null) {
            if (ze.isDirectory()) {
               File newFolder = new File(destinationFolder, ze.getName());
               logger_.debug("Extracting folder \"" + newFolder.getPath() + "\".");
               newFolder.mkdirs();
            } else {
               File destFile = new File(destinationFolder, ze.getName());
               logger_.trace("Extracting file \"" + destFile.getName() + "\".");
               PDFASUtils.toFile(zis, destFile);
            }
            zis.closeEntry();
         }
         zis.close();
         return true;
      } catch (IOException e) {
         throw new ConfigUtilsException(e);
      }
   }

   private static boolean configurationAlreadyExists(String destination) throws ConfigUtilsException, IOException {
      logger_.debug("Checking configuration \"" + destination + "\" already exists (resp. if there are any directories or files that would be overwritten).");
      File destinationFolder = new File(destination);
      if (destinationFolder == null || !destinationFolder.exists()) {
         return false;
      }
      InputStream in = ConfigUtils.class.getClassLoader().getResourceAsStream(Constants.DEFAULT_CONFIGURATION_ZIP_RESOURCE);
      if (in == null) {
         throw new ConfigUtilsException("Unable to find default configuration resource \"" + Constants.DEFAULT_CONFIGURATION_ZIP_RESOURCE + "\".");
      }
      ZipInputStream zis = new ZipInputStream(in);
      ZipEntry ze;
      while ((ze = zis.getNextEntry()) != null) {
         if (ze.isDirectory()) {
            File newFolder = new File(destinationFolder, ze.getName());
            logger_.debug("Checking if folder \"" + newFolder.getPath() + "\" already exists.");
            if (newFolder.exists()) {
               logger_.debug("YES !");
               return true;
            } else {
               logger_.debug("no");
            }
         } else {
            File destFile = new File(destinationFolder, ze.getName());
            logger_.trace("Checking if file \"" + destFile.getName() + "\" already exists.");
            if (destFile.exists()) {
               logger_.trace("YES !");
               return true;
            } else {
               logger_.trace("no");
            }
         }
         zis.closeEntry();
      }
      zis.close();
      return false;
   }
   
   /**
    * Deploys the default configuration to the given destination folder.
    * 
    * @param destination The destination folder.
    * @param overwriteExisting If set <code>true</code> an already existing configuration is overwritten. If <code>false</code> nothing is being copied if the destination folder already exists.
    * @return The folder the configuration has been extracted to or <code>null</code> if the configuration has NOT been deployed.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    */
   public static String deployDefaultConfiguration(String destination, boolean overwriteExisting) throws ConfigUtilsException {
      if (destination == null) {
         throw new NullPointerException("Destination must not be null.");
      }
      if (destination.length() == 0) {
         throw new IllegalArgumentException("Destination must not be empty.");
      }
      return deployFromZIP(destination, overwriteExisting) ? destination : null;
   }
   
   /**
    * Deploys the default configuration to the user's home directory to the subdirectory specified by
    * <code>Constants.Constants.USERHOME_CONFIG_FOLDER</code>.
    * 
    * @param overwriteExisting If set <code>true</code> an already existing configuration is overwritten. If <code>false</code> nothing is being copied if the destination folder already exists.
    * @return The folder the configuration has been extracted to or <code>null</code> if the configuration has NOT been deployed.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    * @see Constants#USERHOME_CONFIG_FOLDER
    */
   public static String deployDefaultConfiguration(boolean overwriteExisting) throws ConfigUtilsException {
      String configdir = System.getProperty(Constants.CONFIG_DIR_SYSTEM_PROPERTY);
      if (configdir == null) {
         configdir = System.getProperty("user.home");
         if (configdir != null) {
            configdir = configdir + File.separator + Constants.USERHOME_CONFIG_FOLDER;            
         }
      }
      if (configdir == null || configdir.length() == 0) {
         return null;
      }
      return deployDefaultConfiguration(configdir, overwriteExisting);
   }

   /**
    * Deploys the default configuration to the user's home directory to the subdirectory specified by
    * <code>Constants.Constants.USERHOME_CONFIG_FOLDER</code>.
    * 
    * @return The folder the configuration has been extracted to or <code>null</code> if the configuration has NOT been deployed.
    * @throws ConfigUtilsException Thrown if there was an error during the deployment of the configuration.
    * @see Constants#USERHOME_CONFIG_FOLDER
    */
   public static String deployDefaultConfiguration() throws ConfigUtilsException {
      return deployDefaultConfiguration(false);
   }

   public static void writeInputStreamToOutputStream(InputStream inputStream, OutputStream outputStream) throws IOException {
      final int bufferSize = 1024;
      byte[] buffer = new byte[bufferSize];
      int len = -1;
      while ((len = inputStream.read(buffer)) != -1) {
         outputStream.write(buffer, 0, len);
      }
      outputStream.flush();
   }
   
   public static String assertFileSeparator(String path) {
      if (path == null) {
         throw new NullPointerException("Path must not be null.");
      }
      if (path.endsWith(File.separator) || path.endsWith("/") || path.endsWith("\\")) {
         return path;
      } else {
         return (path + File.separator);
      }
   }
   
   public static void initializeLogger() {
      String loggerConfiguration = System.getProperty("log4j.configuration"); 
      if (loggerConfiguration != null) {
         logger_.info("No PDF-AS logger configured because a configuration has already been set via system property \"log4j.configuration\" (=\"" + loggerConfiguration + "\").");
         return;
      }
      loggerConfiguration = assertFileSeparator(SettingsReader.CONFIG_PATH) + "log4j.properties";
      File loggerConfigFile = new File(loggerConfiguration);
      if (!loggerConfigFile.exists()) {
         logger_.info("No PDF-AS logger configured because there is no log4j.properties within the pdf-as work dir. Maybe the logger configuration is handled by an outside application (e.g. a web application).");
         return;
      }
      logger_.info("Initializing PDF-AS logger (configuration = \"" + loggerConfiguration + "\").");
      PropertyConfigurator.configure(loggerConfiguration);
   }
   
   public static void printConfigInfo(Log logger) {
      int length = Utils.max(new int[] { SettingsReader.RESOURCES_PATH.length(), SettingsReader.TMP_PATH.length(), SettingsReader.CONFIG_PATH.length(), SettingsReader.CERT_PATH.length() });
      
      String separator = StringUtils.repeat("*", length + 25);
      String infoResources     = " resources path     = \"" + SettingsReader.RESOURCES_PATH + "\"";
      String infoConfiguration = " configuration path = \"" + SettingsReader.CONFIG_PATH + "\"";
      String infoCertStore     = " certstore path     = \"" + SettingsReader.CERT_PATH + "\"";
      String infoTempPath      = " temporary path     = \"" + SettingsReader.TMP_PATH + "\"";
      String encoding          = " file.encoding      = \"" + System.getProperty("file.encoding") + "\"";
 
      if (logger != null) {
         logger.info(separator);
         logger.info(infoResources);
         logger.info(infoConfiguration);
         logger.info(infoCertStore);
         logger.info(infoTempPath);
         logger.info(encoding);
         logger.info(separator);
      } else {
         StringBuffer buffer = new StringBuffer();
         buffer.append(separator).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(infoResources).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(infoConfiguration).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(infoCertStore).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(infoTempPath).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(encoding).append(SystemUtils.LINE_SEPARATOR);
         buffer.append(separator);
         System.out.println(buffer.toString());
      }
   }
   
   public static void printConfigInfo() {
      printConfigInfo(null);
   }
   
}