/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * ----------------------------------------
 */
package fr.tlse.irap.server.serviceDAO.utils;

import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Stack;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import cdpp3dview.types.Tuple3D;

/**
 * Parses an impex tree.xml file
 *
 * @author Jimmy
 */
public class TimeSerieSaxHandler extends DefaultHandler {

    private static Logger LOGGER = Logger.getLogger(TimeSerieSaxHandler.class.getName());
    private String currCharValue;
    private Stack<String> nodeStack = new Stack<String>();
    private int nbParams = 0;
    private int tdCount = 0;
    /** count output columns. If a input is a vector, output will contains more columns */
    private int nbResCols = 0;
    /** list of parameters indexes */
    private int paramIdx[] = null;
    /** list of parameters with vector inside if 1, next 2 values are owned by first one */
    private int paramVectorIdx[] = null;
    private int timeIdx = -1;
    /** param name to extreact */
    private List<String> paramNames;
    protected String[] paramUnit;
    private String[] paramDesc;
    private float[] scDataScale;
    private List<Date> timeValues = new ArrayList<Date>();
    private boolean keepTime = false;
    private List<double[]> values = new ArrayList<double[]>();
    private float minValue = Float.MAX_VALUE;
    private float maxValue = Float.MIN_VALUE;
    private Date currTime = null;
    private String url;
    /** Used to store values on one line. Dim can be more than one for a vector */
    private double currValues[] = null;
    private Date startTime = null;
    private Date stopTime = null;
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    /**
     * reads a time series from a vo table, with Time
     * @param pParamName parameter to extract. Must match one of Field names falues in votable
     */
    public TimeSerieSaxHandler(List<String> pParamNames) {
        super();
        paramNames = pParamNames;
        paramIdx = new int[pParamNames.size()];
        paramVectorIdx = new int[pParamNames.size()];
        paramUnit = new String[pParamNames.size()];
        scDataScale = new float[pParamNames.size()];
        paramDesc = new String[pParamNames.size()];
        nbResCols = pParamNames.size();
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    /**
     * reads a time series from a vo table, with Time
     *
     * @param pParamName parameter to extract. Must match one of Field names falues in votable
     */
    public TimeSerieSaxHandler(String pUrl, List<String> pParamNames) {
        this(pParamNames);
        url = pUrl;
    }

    @Override
    public void startDocument() throws SAXException {
        //LOGGER.info("Start parsing document for "+paramNames.size()+" parameters " + Arrays.toString(paramNames.toArray()));
        //if times are fileld before parse, keep thos values!
        if (timeValues!=null && !timeValues.isEmpty()) {
            keepTime = true;
        }
    }

    @Override
    public void endDocument() throws SAXException {
        //LOGGER.info("End document. Found "+timeValues.size()+" times "+values.size()+" values.");
        //LOGGER.info("Grid min/max = "+minValue+"/"+maxValue);
        
        //set Variable. Build param name from param concatenation
        String lTotName = "";
        
        //rebuild params to build flat array if a column had a vector inside
        String paramUnitOut[] = new String[nbResCols];
        String paramDescOut[] = new String[nbResCols];
        int lNbAddedCols=0;
        for(int i=0;i<paramNames.size();i++) {
            if (paramVectorIdx[i]==1) {
                for(int j=0;j<3;j++) {
                    paramUnitOut[i+lNbAddedCols+j] = paramUnit[i];
                    paramDescOut[i+lNbAddedCols+j] = paramDesc[i];
                    lTotName += paramNames.get(i)+('x'+j)+" ";
                }
                lNbAddedCols += 2;
            } else {
                paramUnitOut[i + lNbAddedCols] = paramUnit[i];
                paramDescOut[i + lNbAddedCols] = paramDesc[i];
                lTotName += paramNames.get(i) + " ";
            }
        }

    }

    @Override
    public void startElement(String uri, String localName,
            String qName, Attributes attributes)
            throws SAXException {
        nodeStack.push(qName);
        //System.out.println("start element    : " + qName);
        //First get values index to parse from fields.
        if ("FIELD".equals(qName.toUpperCase())) {
            if("TIME".equals(attributes.getValue("name").toUpperCase())
                    || "EPOCH".equals(attributes.getValue("name").toUpperCase())) {
                //LOGGER.info("Found time at index "+nbParams);
                timeIdx = nbParams;
            }
            
            //get position of each param
            for (int i=0;i<paramNames.size();i++) {
                String lParamName = paramNames.get(i);
                if (lParamName.toUpperCase().equals(attributes.getValue("name").replace(' ','_').toUpperCase())) {
                    //test AMDA array type
                    if (attributes.getValue("arraysize") != null
                            && attributes.getValue("arraysize").equals("3")) {
                        paramVectorIdx[i] = 1;
                        //extend values columns of 2
                        nbResCols += 2;
                        //System.out.println("Found an AMDA type array value");
                    } else {
                        paramVectorIdx[i] = 0;
                    }
                    paramIdx[i] = nbParams;
                    paramUnit[i] = attributes.getValue("unit");
                    paramDesc[i] = attributes.getValue("ucd");
                    //LOGGER.info("Found parameter "+lParamName+" at index " + nbParams + " " + paramUnit[i] + " " + paramDesc[i]);
                    
                    
                    if (paramUnit[i]==null) {paramUnit[i]="ukn";}
                    //for CLWeb
                    if (paramUnit[i].equals("rt")) {paramUnit[i]="Re";}
                    
                    /****************** temp solution for FMI unit problem **********/
                    scDataScale[i] = 1;
                    if(paramUnit[i].equals("T")) {
                        scDataScale[i] = 1000000000;
                        paramUnit[i] = "nT";
                    }
                    
                    if(paramUnit[i].equals("m/s")) {
                        scDataScale[i] = 1/1000f;
                        paramUnit[i] = "km/s";
                    }
                }
            }
            nbParams++;
        }
        
        if ("TR".equals(qName.toUpperCase())) {
            currValues = new double[nbResCols];
        }
        
        /* check end of head parsing do do some usefull stuf */
        if ("DATA".equals(qName.toUpperCase())) {
            //if  a time column found, ignore an eventually pre filled array
//removed for SINP because return
//	            if(timeIdx!=-1) {
//	                timeValues.clear();
//	            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        /*System.out.println("end element      : " + qName);*/
        nodeStack.pop();
        if(currCharValue==null) {
            return;
        }

        /*
         * Then get values at index got from field values.
         */
        if ("TD".equals(qName.toUpperCase())) {
            //get Values
            if (tdCount == timeIdx) {
                try {
                    currTime = sdf.parse(currCharValue);
//System.out.println("Found time: " + currCharValue + " at " + tdCount);

                    if (startTime == null) {
                        startTime = currTime;
                    } else {
                        stopTime = currTime;
                    }
                    
                } catch (ParseException ex) {
                    LOGGER.log(Level.SEVERE, "Error parsing date from "+currCharValue+" : " + ex.getMessage(), ex);
                }
            }

            //get desired value
            for(int i=0;i<paramIdx.length;i++) {
                if (tdCount == paramIdx[i]) {
                    //if(!"NaN".equals(currCharValue.trim())) {
                    //}
                    if (paramVectorIdx[i] == 1) {
                        //Add a vector ***********managed if only 1 value is fetched*************
                        String[] lVVals = currCharValue.trim().split("\\s+");
                        currValues[i] = Float.parseFloat(lVVals[0]) * scDataScale[i];
                        currValues[i+1] = Float.parseFloat(lVVals[1]) * scDataScale[i];
                        currValues[i+2] = Float.parseFloat(lVVals[2]) * scDataScale[i];
                    } else {
                        //add a scalar
                        currValues[i] = Float.parseFloat(currCharValue.trim()) * scDataScale[i];
                    }
                }
            }
            
            tdCount++;
        }
        if ("TR".equals(qName.toUpperCase())) {
            if(currTime!=null && !keepTime) {
//System.out.println("Adding time " + currTime);
                timeValues.add(currTime);
            }
            if (currValues != null) {
//System.out.println("Adding "+currValues[0] +" " + currValues[1] + " /"+values.size());
                values.add(currValues);
            }
            tdCount = 0;
        }
    }

    @Override
    public void characters(char ch[], int start, int length)
            throws SAXException {
        currCharValue = new String(ch, start, length);
    }

    /**
     * Set time values.
     * Can be used if it is not contained in file for generic processing.
     * @return list of dates
     */
    public void setTimeValues(List<Date> pTimeValues) {
        timeValues = pTimeValues;
    }
    
    /**
     * Return times read from file or set with setTimeValues.
     * @return list of dates
     */
    public List<Date> getTimeValues() {
        return timeValues;
    }

    /**
     * Return values read from file 
     * @return list of double array
     */
    public List<double[]> getValues() {
        return values;
    }

    
    /**
     * Return values read from file 
     * @return list of Tuple3D values
     */
    public List<Tuple3D> getT3DValues() {
	    List<Tuple3D> data = new ArrayList<Tuple3D>();
		for (double[] value:values) {
			data.add(new Tuple3D(value[0], value[1], value[2]));
		}
		return data;
    }
    
    /**
     * 
     * @throws Exception
     */
    public void open() throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = factory.newSAXParser();
        URL lUrl = new URL(url);
		try {
			saxParser.parse(new GZIPInputStream(lUrl.openStream()), this);
		}
		catch (Exception e) {
			saxParser.parse(lUrl.openStream(), this);
		}
    }
        
    /**
     * @return the minValue
     */
    public float getMinValue() {
        return minValue;
    }

    /**
     * @return the maxValue
     */
    public float getMaxValue() {
        return maxValue;
    }

    /**
     * @return the paramUnit
     */
    public String[] getParamUnit() {
        return paramUnit;
    }

}
