/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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.
 *
 * Copyright (c) 2006 - 2009 Pentaho Corporation and Contributors.  All rights reserved.
 */

package org.pentaho.reporting.libraries.fonts.encoding.generator;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;

import org.pentaho.reporting.libraries.fonts.LibFontBoot;
import org.pentaho.reporting.libraries.fonts.encoding.External8BitEncodingData;
import org.pentaho.reporting.libraries.base.config.DefaultConfiguration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A simple sourcecode generator.
 *
 * @author Thomas Morgner
 */
public class EncodingGenerator
{
  private static final Log logger = LogFactory.getLog(EncodingGenerator.class);

  private File targetDirectory;
  private DefaultConfiguration propertySet;
  private File sourceDirectory;
  public static final Integer ZERO = new Integer(0);

  public EncodingGenerator(final File targetDirectory,
                           final File sourceDirectory) throws IOException
  {
    if (targetDirectory == null)
    {
      throw new NullPointerException();
    }
    if (targetDirectory.isDirectory() == false)
    {
      throw new IllegalArgumentException("Not a directory: " + targetDirectory);
    }
    if (targetDirectory.canWrite() == false)
    {
      throw new IllegalArgumentException("Not writable: " + targetDirectory);
    }
    if (sourceDirectory == null)
    {
      throw new NullPointerException();
    }
    if (sourceDirectory.isDirectory() == false)
    {
      throw new IllegalArgumentException("Not a directory: " + sourceDirectory);
    }
    if (sourceDirectory.canRead() == false)
    {
      throw new IllegalArgumentException("Not readable: " + sourceDirectory);
    }

    this.sourceDirectory = sourceDirectory;
    this.targetDirectory = targetDirectory;

    final InputStream propIn = ObjectUtilities.getResourceRelativeAsStream
            ("encodings.properties", EncodingGenerator.class);
    propertySet = new DefaultConfiguration();
    try
    {
      propertySet.load(propIn);
    }
    finally
    {
      propIn.close();
    }
  }

  public void generatedFormatA(final Properties specifications,
                               final BufferedReader input,
                               final OutputStream output)
          throws IOException
  {
    if (input == null)
    {
      throw new NullPointerException();
    }
    if (output == null)
    {
      throw new NullPointerException();
    }
    if (specifications == null)
    {
      throw new NullPointerException();
    }

    final Integer[] data = new Integer[256];
    data[0] = ZERO;

    String s = input.readLine();
    while (s != null)
    {
      if (s.length() > 0 && s.charAt(0) == '#')
      {
        s = input.readLine();
        continue;
      }

      final StringTokenizer strtok = new StringTokenizer(s);
      if (strtok.hasMoreTokens() == false)
      {
        s = input.readLine();
        continue;
      }
      final String sourceChar = strtok.nextToken().trim();
      if (sourceChar.length() == 0)
      {
        s = input.readLine();
        continue;
      }

      final Integer sourceVal = Integer.decode(sourceChar);
      if (strtok.hasMoreTokens())
      {
        final String targetChar = strtok.nextToken();
        if ((targetChar.length() > 0 && targetChar.charAt(0) == '#') == false)
        {
          data[sourceVal.intValue()] = Integer.decode(targetChar);
        }
      }

      s = input.readLine();
    }

    final int[] indices = new int[256];
    final int[] values = new int[256];

    int index = 0;
    int prevIdx = 0;
    for (int i = 1; i < data.length; i++)
    {
      final Integer integer = data[i];
      if (integer == null)
      {
        // no entry ... this means, we use the standard mapping ..
        continue;
      }

      final Integer prev = data[prevIdx];
      values[index] = integer.intValue() - prev.intValue();
      indices[index] = i - prevIdx;

      prevIdx = i;
      index += 1;
    }

    final ObjectOutputStream oout = new ObjectOutputStream(output);
    oout.writeObject(new External8BitEncodingData(indices, values));
    oout.flush();
  }

  public void generateAll () throws IOException
  {
    final Iterator keys = propertySet.keySet().iterator();
    while (keys.hasNext())
    {
      final String key = (String) keys.next();
      if (key.startsWith("encodings.") == false)
      {
        logger.info("Encoding prefix not found.");
        continue;
      }
      if (key.endsWith(".Filename") == false)
      {
        continue;
      }
      try
      {
        generate(key.substring(0, key.length() - 8));
      }
      catch(Exception e)
      {
        logger.warn ("Failed to generate Encoding " + key, e);
      }
    }
  }

  public void generate(final String prefix) throws IOException
  {
    final String filename = propertySet.getConfigProperty(prefix + "Filename");
    final String className = propertySet.getConfigProperty(prefix + "ClassName");
    final String encoding = propertySet.getConfigProperty(prefix + "Encoding");

    if (filename == null)
    {
      logger.info ("Not generating Encoding: " + prefix + ": Filename missing");
      return;
    }
    if (className == null)
    {
      logger.info ("Not generating Encoding: " + prefix + ": ClassName missing");
      return;
    }
    if (encoding == null)
    {
      logger.info ("Not generating Encoding: " + prefix + ": Encoding missing");
      return;
    }

    final Properties properties = new Properties();
    properties.setProperty("name", className);
    properties.setProperty("source", filename);
    properties.setProperty("encoding", encoding);

    final File sourceFile = new File(sourceDirectory, className + ".ser");
    final File targetFile = new File(targetDirectory, filename);

    System.out.println("Generated Java File:  " + sourceFile);
    System.out.println("Source Specification: " + targetFile);
    final BufferedOutputStream bw =
            new BufferedOutputStream(new FileOutputStream(sourceFile));

    final BufferedReader input = new BufferedReader(new FileReader(targetFile));
    generatedFormatA(properties, input, bw);
    input.close();
    bw.close();
  }

  public void generatePropertyIndex ()
          throws IOException
  {
    final Properties props = new Properties();
    final Iterator keys = propertySet.keySet().iterator();
    while (keys.hasNext())
    {
      final String key = (String) keys.next();
      if (key.startsWith("encodings.") == false)
      {
        logger.info("Encoding prefix not found.");
        continue;
      }
      if (key.endsWith(".Filename") == false)
      {
        continue;
      }
      try
      {
        generateEncodingEntry(props, key.substring(0, key.length() - 8));
      }
      catch(Exception e)
      {
        logger.warn ("Failed to generate Encoding " + key, e);
      }
    }

    final File sourceFile = new File(sourceDirectory, "encodings.properties");
    final FileOutputStream fout = new FileOutputStream(sourceFile);
    props.store(fout, "Generated on " + new Date());
    fout.close();
  }

  private void generateEncodingEntry (final Properties props, final String prefix)
  {
    final String className = propertySet.getConfigProperty(prefix + "ClassName");
    final String encoding = propertySet.getConfigProperty(prefix + "Encoding");

    if (className == null)
    {
      logger.info ("Not generating Encoding: " + prefix + ": ClassName missing");
      return;
    }
    if (encoding == null)
    {
      logger.info ("Not generating Encoding: " + prefix + ": Encoding missing");
      return;
    }
    props.setProperty(encoding, className + ".ser");
  }

  public static void main(final String[] args) throws IOException
  {
    if (args.length < 2)
    {
      throw new IllegalArgumentException("Need two parameters: EncodingsDirectory and Source-Directory");
    }

    final String encodingDirName = args[0];
    final String sourceDirName = args[1];

    LibFontBoot.getInstance().start();
    final File encodingsDir = new File(encodingDirName).getAbsoluteFile();
    final File sourceDir = new File (sourceDirName).getAbsoluteFile();
    final EncodingGenerator gen = new EncodingGenerator (encodingsDir, sourceDir);
    gen.generateAll();
    gen.generatePropertyIndex();
  }

}
