/*
 * Copyright 
 *   2008 axYus - www.axyus.com
 *   2008 L. Meckert - laurent.meckert@axyus.com
 * This file is part of XEMELIOS.
 * 
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * XEMELIOS 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package fr.gouv.finances.cp.xemelios.export;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;

import fr.gouv.finances.cp.utils.Amount;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.config.ElementModel;
import fr.gouv.finances.cp.utils.StringUtilities;
import fr.gouv.finances.cp.xemelios.utils.HtmlTableWriter;
import fr.gouv.finances.cp.xemelios.common.Constants;

import java.io.IOException;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import org.apache.log4j.Logger;

public class HtmlTableExporter extends ExportUndertaker {
   private static final Logger logger = Logger.getLogger(HtmlTableExporter.class);
   private static final int MAX_ROWS_PER_SHEET = 65000;// 65000 ; // test   30 
   // contiendra la liste des sheets XLS, dans l'ordre devant être créé (par ex : SHEET_Bordereau, SHEET_Titre, SHEET_Ligne)
   private Hashtable<String, Vector<HtmlSheet>> sheets = new Hashtable<String, Vector<HtmlSheet>>();
   private Exporter caller;
   private String workbookPrefix = null;
   private HtmlSheet mainSheet;
   protected SimpleDateFormat df = new SimpleDateFormat(System.getProperty("xemelios.date.format", "dd/MM/yyyy"));

   public HtmlTableExporter(Exporter caller) {
      this.caller = caller;
   }
   
   protected String getModelFileName() {
       return "/modeleExportXemelios.xls";
   }

   // Fonctions appellees par Exporter.java
   /**
    * Initialisation
    */
   void initialize() throws Exception {
      /**
       * 
       *  Version rudimentaire pour demarrer
       *  A partir du nom de fichier demande, deduit le prefixe 
       *  utilise par tous les fchiers xls. 
       */

      // Pour que l'appelant soit content je lui genene  un fichier excel vide
      // NON je copie un modele
      String dirRessources = System.getProperty(Constants.SYS_PROP_RESOURCES_LOCATION);
      // TODO ajout correct du separateur de path
      recopieFichier(dirRessources + getModelFileName(), caller.fDest.getPath(), true);



      workbookPrefix = StringUtilities.removeFileNameSuffix(caller.fDest.getPath());

   }

   void recopieFichier(String src, String dst, boolean overwrite) {
      FileChannel in = null; // canal d'entrée
      FileChannel out = null; // canal de sortie

      try {
         // Init
         in = new FileInputStream(src).getChannel();
         out = new FileOutputStream(dst).getChannel();

         // Copie depuis le in vers le out
         in.transferTo(0, in.size(), out);
      } catch (Exception e) {
         e.printStackTrace(); // n'importe quelle exception
      } finally { // finalement on ferme
         if (in != null) {
            try {
               in.close();
            } catch (IOException e) {
            }
         }
         if (out != null) {
            try {
               out.close();
            } catch (IOException e) {
            }
         }
      }
   }

   /**
    * Obsolete 
    */
   void addRefInMainSheet(String libelle, boolean addPrefix) {
      String fileName;
      if (addPrefix) {
         fileName = workbookPrefix + "_" + libelle + ".xls";
      } else {
         fileName = libelle + ".xls";
      }

      String href = "<A HREF=\"file:///" + fileName + "\">" + libelle + "</A>";
      /*
        mainSheet.addEntry(href, 0);
        mainSheet.flushLine();
      */
   }

   /**
    * Crée un <tt>HtmlSheet</tt> et ajoute la liste des entetes de colonne
    * @param eem
    * @throws Exception
    */
   void buildSheet(ExportElementModel eem) throws Exception {
      Vector<HtmlSheet> v = sheets.get(eem.getId());
      if (v == null) {
         v = new Vector<HtmlSheet>();
      }
      sheets.put(eem.getId(), v);

      String fileName = workbookPrefix + "_" + eem.getLibelle(); // + ".xls";

      HtmlSheet sheet = new HtmlSheet();
      sheet.open(fileName);

      addRefInMainSheet(eem.getLibelle(), true);
      v.add(sheet);

      for (ExportElementModel e : eem.getEnfants()) {
         buildSheet(e);
      }
      caller.firstSheetId = eem.getId();
   }

   /**
    * Apporte des finitions 
    */
   void finish() throws Exception {
      // pour l'instant rien
   }

   /**
    * Fermeture et nettoyage
    */
   void close() {
      Enumeration elems = sheets.keys();
      while (elems.hasMoreElements()) {
         String k = (String) elems.nextElement();
         Vector<HtmlSheet> v = sheets.get(k);
         for (HtmlSheet s : v) {

            s.close();
         }

      }
      //   mainSheet.close();
   }

   /**
    * Verifie l'existence d'unr feuille dans le classeur
    * @param sheetId le nom de la feuille
    */
   boolean sheetExists(String sheetId) {
      Vector<HtmlSheet> v = sheets.get(sheetId);
      if (v != null) {
         return true;
      } else {
         return false;
      }
   }

   /**
    * renvoie le dernier numero de ligne utilisee dans la derniere feuille de nom sheetId
    */
   int getLastLineLastSheet(String sheetId) {
      // suppose qu'on a appele sheetExists(sheetId) 
      HtmlSheet ws = getLastSheet(sheetId);
      return (ws.getRows() < 2 ? 2 : ws.getRows());
   }

   /**
    * Remplit une cellule avec la valeur correctement formatée.
    * @param sheet
    * @param o
    * @param ligne
    * @param col
    * @throws WriteException
    */
   void writeCell(String sheetId, Object o, int line, int col, boolean bypassLineRenum) throws Exception {

      //  System.out.println("writeCell " + sheetId + ",line=" + line + ",col=" + col + "bypassLineRenum=" + bypassLineRenum);
      int newline = 0;
      if (bypassLineRenum) {
         newline = line;
      } else {
         int _ligne = caller.getLastLine(sheetId);
         if (_ligne >= (MAX_ROWS_PER_SHEET * sheets.get(sheetId).size())) {
            //                System.out.println("SPLITSHEET ( " + sheetId + ")");
            splitSheet(sheetId);
         }

         // renumérotation de la ligne
         newline = _ligne;
         newline = _ligne % (MAX_ROWS_PER_SHEET) + 2;
         //  System.out.println("writeCell ligne = " + line + ", _ligne=" + _ligne + ",newline= " + newline + ",Col=" + col);
      }

      HtmlSheet sheet = getLastSheet(sheetId);
      String s = formatted(o, line);
      // Si line = 0  = c'est un titre
      if (line == 0 ) {
         sheet.addTitle(s, col);
      } else if (line == 1) {
         // Si line = 1  = c'est un  nom de champ
         sheet.addName(s, col);
      } else {
         // sinon c'est une ligne normale
         sheet.addEntry(s, col);
      }


   }

   /**
    * reunit des cellules dans la derniere des feuilles de nom sheetId
    * @param sheetId = nom de la feuille 
    * @param  col1 = colonne de depart
    * @param row1 = ligne de depart
    * @param col2 = colonne d'arrivee
    * @param row2= ligne d'arrivee
    */
   void mergeCells(String sheetId, int col1, int row1, int col2, int row2) {

      HtmlSheet _ws = getLastSheet(sheetId);

      _ws.mergeCells(col1, row1, col2, row2);



   }

   void flushLine(String sheetId) {
      HtmlSheet _ws = getLastSheet(sheetId);
      _ws.flushLine();
   }


   // Fonctions a usage interne mises en protected on sait jamais
   /**
    * Formatage de l'objet a ecrire
    */
   protected String formatted(Object o, Integer line) {

      // Reconnaitre seleon me type
      String rawStr;
      Integer styleId;
      Integer level = 3;
      if (o instanceof java.lang.Integer) {
         rawStr = ((Integer) o).toString();
         styleId = HtmlTableWriter.INTEGER_CELLFORMAT;
      } else if (o instanceof java.lang.Float) {
         rawStr = ((Float) o).toString();
         styleId = HtmlTableWriter.FLOAT_CELLFORMAT;
      } else if (o instanceof java.util.Date) {
         rawStr = df.format(o);
         styleId = HtmlTableWriter.DATE_CELLFORMAT;
      } else if (o instanceof Amount) {
         Double d = new Double(((Amount) o).doubleValue());
         rawStr = d.toString();
         styleId = HtmlTableWriter.FLOAT_CELLFORMAT;
      } else if (o instanceof java.lang.String) {
         rawStr = (String) o;
         styleId = HtmlTableWriter.TEXT_CELLFORMAT;
      } else {
         rawStr = (String) o;
         styleId = -1;
      }

      // mettre en gras les titres
      if (line == 0) {
         level = 1;
      }
      if (line == 1) {
         level = 2;
      }
      return HtmlTableWriter.buildCell(rawStr, styleId, level);
   }

   /**
    * Numero d'ordre suivant celui de la feuille de nom id
    */
   protected int afterSheet(String id) {
      /*	String[]names = workbook.getSheetNames();
                for (int i=1; i<names.length; i++) {
                if (names[i].equals(id)) return i+1;
                }
                return 1;*/
      return 1;
   }

   /**
    * Récupére la derniere feuille correspondant a
    * l'objet de nom id (c'est celle en cours d'edition)
    */
   protected HtmlSheet getLastSheet(String sheetId) {
      Vector<HtmlSheet> v = sheets.get(sheetId);
      if (v != null) {
         return v.get(v.size() - 1);
      } else {
         return null;
      }
   }

   /**
    * Récupére la premiere feuille correspondant au premier parent de
    * l'objet de nom id
    */
   protected HtmlSheet getRootSheetBefore(String id) {
      HtmlSheet ret = null;
      ElementModel e = caller.getEMFromId(id);
      if (e.getSimpleParent() != null) {
         String pName = e.getSimpleParent().getElement();
         ret = sheets.get(pName).get(0);
      }
      return ret;
   }

   /**
    * creee une nouvelle feuille de nom id suivi d'un _  er d'un numero d'ordre
    * appelle lorsque la feuille 
    * precedente de meme nom "deborde"
    */
   protected HtmlSheet splitSheet(String id) {
      Vector<HtmlSheet> v = sheets.get(id);
      HtmlSheet wsRef = v.get(0);
      HtmlSheet otherRef = getLastSheet(id);

      wsRef.close();

      HtmlSheet ws = new HtmlSheet();


      NumberFormat nf = NumberFormat.getInstance(); // Get Instance of NumberFormat
      nf.setMinimumIntegerDigits(2);  // The minimum Digits required is 5
      nf.setMaximumIntegerDigits(3); // The maximum Digits required is 5

      String sb = (nf.format((long) v.size()));



      String fname = wsRef.getName() + "_" + sb;
      //        System.out.println("splitSheet => fname = " + fname);



      ws.open(fname);

      addRefInMainSheet(fname, false);





      ws.titles = wsRef.titles;
      ws.names = wsRef.names;
      ws.maxTitle = wsRef.maxTitle;
      ws.maxName = wsRef.maxName;
      //    ws.maxEntry = wsRef.maxEntry;

      ws.flushTitles();
      ws.flushNames();

      v.add(ws);
      return ws;
   }

   protected class HtmlSheet {

      String name; // nom de la "feuille" 
      HtmlTableWriter writer; // objet consituant les lignes et les ecrivant dans le fichier
      String[] titles; // pour noter la ligne de titre
      String[] names;  // pour noter la ligne de noms de champs
      String[] entries; // pour noter la ligne en cours
      int curTitle;
      int curName;
      int curEntry;
      int maxTitle;
      int maxName;
      int maxEntry;
      int colCount;
      int rowCount;

      boolean titlesFlushed;
      boolean namesFlushed;

      void open(String fname) {
         try {
            FileWriter out = new FileWriter(fname + ".xls");
            writer = new HtmlTableWriter(out);
         } catch (IOException e) {
            //                System.out.println("IOException : " + fname + ".xls");
            logger.error("HtmlSheet.open("+fname+")",e);
            //                System.out.println(e);
         }
         // Version initial tableau surdimensionnnes hum=
         name = fname;
         titles = new String[100];
         names = new String[100];
         entries = new String[100];
         maxTitle = 0;
         maxName = 0;
         maxEntry = 0;
         rowCount = 0;
         titlesFlushed = false;
         namesFlushed = false;
      }

      void close() {
         if (writer != null) {
            try {
               writer.close();

            } catch (IOException e) {
               //                    System.out.println("IOException : " + e);
               logger.error("HtmlSheet.close()",e);
            }
            writer = null;
         }
      }

      String getName() {
         return name;
      }

      int getRows() {
         return rowCount;
      }

      void addTitle(String s, int col) {
         if (!titlesFlushed) {
            if (col >= titles.length || maxTitle >= titles.length) {
               // cas ou il faut redimensionner
               int oldlength = titles.length;
               int newlength = oldlength + oldlength / 2;
               String[] tm = new String[newlength];
               for (int i = 0; i < oldlength; ++i) {
                  tm[i] = titles[i];
               }
               titles = tm;
            }
            while (maxTitle + 1 < col) {
               maxTitle++;
               titles[maxTitle] = formatted("", 0); // pour simuler la presentation en Excel pur

            }
            titles[col] = s;
            if (col > maxTitle) {
               maxTitle = col;
            }

         }
      }

      void addName(String s, int col) {
         if (!namesFlushed) {
            if (col >= names.length || maxName >= names.length) {
               // cas ou il faut redimensionner
               int oldlength = names.length;
               int newlength = oldlength + oldlength / 2;
               String[] tm = new String[newlength];
               for (int i = 0; i < oldlength; ++i) {
                  tm[i] = names[i];
               }
               names = tm;
            }
            while (maxName + 1 < col) {
               maxName++;
               names[maxName] = formatted("", 1);
            }
            names[col] = s;
            if (col > maxName) {
               maxName = col;
            }

         }
      }

      void addEntry(String s, int col) {
         if (col >= entries.length || maxEntry >= entries.length) {
            // cas ou il faut redimensionner
            int oldlength = entries.length;
            int newlength = oldlength + oldlength / 2;
            String[] tm = new String[newlength];
            for (int i = 0; i < oldlength; ++i) {
               tm[i] = entries[i];
            }
            entries = tm;
         }


         while (maxEntry + 1 < col) {
            maxEntry++;
            entries[maxEntry] = formatted("", 999);
         }
         entries[col] = s;
         if (col > maxEntry) {
            maxEntry = col;
         }
      }

      void mergeCells(int col1, int row1, int col2, int row2) {
         /*   INUTILE  if( col1 == col2) { // on ne sait pas faire autrement!!
              int i = row1 + 1;
              while(i < row2) {
              if(col1 == 0)   { // titres  
              titles[i] = formatted("", 0); 
              } else   if(col1 == 1)    {// noms de champs 
              names[i] = formatted("", 0); 
              }
              ++i;   
              }
              }*/
      }

      void flushTitles() {
         writer.writeNext(titles, maxTitle);
         titlesFlushed = true;
         ++rowCount;
      }

      void flushNames() {
         writer.writeNext(names, maxName);
         namesFlushed = true;
         ++rowCount;
      }

      void flushLine() {
         if (!titlesFlushed) {
            flushTitles();
         }
         if (!namesFlushed) {
            flushNames();
         }

         writer.writeNext(entries, maxEntry);
         maxEntry = 0;
         ++rowCount;
      }
   }

    
    

   ;
}
