package org.openswing.swing.pivottable.server;

import org.openswing.swing.pivottable.java.*;
import org.openswing.swing.pivottable.tablemodelreaders.server.Reader;
import org.openswing.swing.message.receive.java.*;
import java.util.*;
import org.openswing.swing.pivottable.functions.java.GenericFunction;


/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: Engine used to generate a PivotTableModel, starting from a PivotTableParameters.</p>
 * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
 *
 * <p> This file is part of OpenSwing Framework.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the (LGPL) Lesser General Public
 * License as published by the Free Software Foundation;
 *
 *                GNU LESSER GENERAL PUBLIC LICENSE
 *                 Version 2.1, February 1999
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *       The author may be contacted at:
 *           maurocarniel@tin.it</p>
 *
 * @author Mauro Carniel
 * @version 1.0
 */
public class PivotTableEngine {

  /** data model reader */
  private Reader reader = null;


  /**
   * @param reader Data model reader
   */
  public PivotTableEngine(Reader reader) {
    this.reader = reader;
  }


  /**
   * @param pars parameters used in PivotTableEngine to generate the PivotTableModel
   * @return VOResponse that contains a PivotTableModel generated starting from PivotTableParameters; ErrorResponse in case of errors on analyzing data
   */
  public final Response getPivotTableModel(PivotTableParameters pars) {
    HashSet cols = new HashSet();
    for(int i=0;i<reader.getColumnCount();i++)
      cols.add(reader.getColumnName(i));

      // check whether some row/column/data fields are unknown...
    for(int i=0;i<pars.getRowFields().size();i++)
      if (!cols.contains(((RowField)pars.getRowFields().get(i)).getColumnName())) {
        return new ErrorResponse( ((RowField)pars.getRowFields().get(i)).getColumnName()+"' row field is not defined in data model: Pivot Table cannot be created." );
      }
    for(int i=0;i<pars.getColumnFields().size();i++)
      if (!cols.contains(((ColumnField)pars.getColumnFields().get(i)).getColumnName())) {
        return new ErrorResponse( ((ColumnField)pars.getColumnFields().get(i)).getColumnName()+"' column field is not defined in data model: Pivot Table cannot be created." );
      }
    for(int i=0;i<pars.getDataFields().size();i++)
      if (!cols.contains(((DataField)pars.getDataFields().get(i)).getColumnName())) {
        return new ErrorResponse( ((DataField)pars.getDataFields().get(i)).getColumnName()+"' data field is not defined in data model: Pivot Table cannot be created." );
      }
    if (pars.getRowFields().size()==0) {
      return new ErrorResponse("At least one field must be defined as row field: Pivot Table cannot be created.");
    }
    if (pars.getColumnFields().size()==0) {
      return new ErrorResponse("At least one field must be defined as column field: Pivot Table cannot be created.");
    }
    if (pars.getDataFields().size()==0) {
      return new ErrorResponse("At least one field must be defined as data field: Pivot Table cannot be created.");
    }

    // index input data model column names...
    HashMap indexes = new HashMap();
    for(int j=0;j<reader.getColumnCount();j++) {
      indexes.put(reader.getColumnName(j),new Integer(j));
    }

    // index row fields indexes...
    int[] rowFieldsIndexes = new int[pars.getRowFields().size()];
    for(int j=0;j<pars.getRowFields().size();j++) {
       rowFieldsIndexes[j] = ((Integer)indexes.get(((RowField)pars.getRowFields().get(j)).getColumnName())).intValue();
    }

    // index data fields indexes...
    int[] dataFieldsIndexes = new int[pars.getDataFields().size()];
    for(int j=0;j<pars.getDataFields().size();j++) {
       dataFieldsIndexes[j] = ((Integer)indexes.get(((DataField)pars.getDataFields().get(j)).getColumnName())).intValue();
    }

    // index column fields indexes...
    int[] colFieldsIndexes = new int[pars.getColumnFields().size()];
    for(int j=0;j<pars.getColumnFields().size();j++) {
       colFieldsIndexes[j] = ((Integer)indexes.get(((ColumnField)pars.getColumnFields().get(j)).getColumnName())).intValue();
    }

    // create Pivot TableModel...
    RowGenericNode hroot = new RowGenericNode();
    GlobalColGenericNode vroot = new GlobalColGenericNode();
    PivotTableModel model = new PivotTableModel(hroot,vroot);

    // read all data in input...
    if (!reader.initializeScrolling(pars.getInputFilter()))
      return new ErrorResponse("Error on reading data on input.");
//    StringBuffer hpath = new StringBuffer();
    GenericNodeKey hpath = null;
    RowGenericNode hnode = null;
    ColGenericNode vnode = null;
    GlobalColGenericNode globalvnode = null;
    RowGenericNode hparentNode = null;
    ColGenericNode vparentNode = null;
    GlobalColGenericNode globalvparentNode = null;
    RowField rowField = null;
    ColumnField colField = null;
    Object hvalue = null;
    Object vvalue = null;
    HashMap htreeNodes = new HashMap();
    HashMap vtreeNodes = null;
    HashMap globalvtreeNodes = new HashMap();
    GenericFunction[] gf = null;
    GenericNodeKey vpath = null;
    while(reader.nextRow(pars.getInputFilter())) {
      // read row...
      hparentNode = hroot;
      hpath = new GenericNodeKey();

      // for each row: parse row data, along row fields...
      for(int j=0;j<pars.getRowFields().size();j++) {
        rowField = (RowField)pars.getRowFields().get(j);
        hvalue = rowField.getAggregator().decodeValue(reader.getValueAt(rowFieldsIndexes[j]));
        if (hvalue==null)
          hvalue = "";

        hpath = hpath.appendKey(hvalue);
        hnode = (RowGenericNode)htreeNodes.get(hpath);
        if (hnode==null) {
          hnode = new RowGenericNode(hpath);
          htreeNodes.put(hpath,hnode);
          hparentNode.add(hnode);
        }
        vtreeNodes = hnode.getVtreeNodes();

        // for each row field node, parse row data, along column fields and calculate data field values...
        vparentNode = null;
        globalvparentNode = vroot;
        vpath = new GenericNodeKey();
        for(int y=0;y<pars.getColumnFields().size();y++) {
          colField = (ColumnField)pars.getColumnFields().get(y);
          vvalue = colField.getAggregator().decodeValue(reader.getValueAt(colFieldsIndexes[y]));
          if (vvalue==null)
            vvalue = "";

          vpath = vpath.appendKey(vvalue);
          vnode = (ColGenericNode)vtreeNodes.get(vpath);
          if (vnode==null) {
            gf = new GenericFunction[dataFieldsIndexes.length];
            for (int u = 0; u < dataFieldsIndexes.length; u++)
              try {
               gf[u] = (GenericFunction) ( (DataField) pars.getDataFields().get(u)).getFunction().getClass().newInstance();
              }
              catch (Throwable ex) { return new ErrorResponse("Error while analyzing data."); }

            vnode = new ColGenericNode(vpath,gf);
            vtreeNodes.put(vpath,vnode);
            if (vparentNode!=null)
              vparentNode.add(vnode);
            else
              hnode.setColsParentNode(vnode);
          }
          else
            gf = (GenericFunction[])vnode.getGenericFunctions();

          fillRow(pars,dataFieldsIndexes,gf);
          vparentNode = vnode;

          // moreover, create a global column fields hierarchy, without associating data field values...
          globalvnode = (GlobalColGenericNode)globalvtreeNodes.get(vpath);
          if (globalvnode==null) {
            globalvnode = new GlobalColGenericNode(vpath);
            globalvtreeNodes.put(vpath,globalvnode);
            globalvparentNode.add(globalvnode);
          }
          globalvparentNode = globalvnode;

        } // end for on colFields

        hparentNode = hnode;
      } // end for on rowFields
    } // end while


    return new VOResponse(model);
  }


  private void fillRow(PivotTableParameters pars,int[] dataFieldsIndexes,GenericFunction[] gf) {
    Number n = null;
    for(int j=0;j<pars.getDataFields().size();j++) {
      n = (Number)reader.getValueAt(dataFieldsIndexes[j]);
      gf[j].processValue(reader.getValueAt(dataFieldsIndexes[j]));
    }
  }






}
