/* File: StreamYScanDescriptor.java
 * Copyright (C) 2002-2003 The University of Iowa
 * Created by: Jeremy Faden <jbf@space.physics.uiowa.edu>
 *             Jessica Swanner <jessica@space.physics.uiowa.edu>
 *             Edward E. West <eew@space.physics.uiowa.edu>
 *
 * This file is part of the das2 library.
 *
 * das2 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.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.das2.stream;

import org.das2.datum.DatumVector;
import org.das2.datum.Units;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.das2.datum.LoggerManager;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class StreamYScanDescriptor implements SkeletonDescriptor, Cloneable {

	private static final Logger logger = LoggerManager.getLogger("das2.d2s.yscan");
	private static final String g_sCkAry[] = {
		"name","type","nitems","yTags","yTagInterval","yTagMin","yTagMax"
	};
	
    private Units yUnits = Units.dimensionless;
    private Units zUnits = Units.dimensionless;
    private double[] yTags;
    private int nitems;
    private String name = "";
    private DataTransferType transferType = DataTransferType.SUN_REAL4;
	 Map properties= new HashMap();

    public StreamYScanDescriptor( Element element ) throws StreamException {
        if ( element.getTagName().equals("yscan") ) {
            processElement(element);
        }
        else {
            processLegacyElement(element);
        }
    }
   
	// make y tags using interval specification.  This version can walk down 1 of there
	// paths: Calc tags from 0, calc tags form a minimum value, calc tags from a 
	// maximum value
	private double[] makeYtagsUsingInterval(Element element, int nItems, double rInterval) 
		throws StreamException
	{
		String sMin = element.getAttribute("yTagMin");
		String sMax = element.getAttribute("yTagMax");
		int i;
		double[] lYtags = new double[nItems];
		if( sMin.isEmpty() && sMax.isEmpty()){
			for(i = 0; i<nItems;i++) lYtags[i] = rInterval*i;
		}
		else{
			if(sMax.isEmpty()){
				double rMin = Double.parseDouble(sMin);
				for(i = 0; i<nItems;i++) lYtags[i] = rInterval*i + rMin;
			}
			else{
				double rMax = Double.parseDouble(sMin);
				for(i = 0; i<nItems;i++) lYtags[(nItems - 1) - i] = rMax - rInterval*i;
			}
		}
		return lYtags;
	}
	 
	private void processElement(Element element) throws StreamException{

		boolean doCheck = false;
		if(doCheck){
		 //name, units, nitems, yUnits, yTags, zUnits are required, though they can be null
			//name, units, and type are required, though they can be null
			for(String s : g_sCkAry){
				if(!element.hasAttribute(s)){
					throw new StreamException("Das2 Stream Format error: Required Attribute '" + s
						+ "' missing in <" + element.getTagName() + "> plane.");
				}
			}
            if ( !element.hasAttribute("yUnits") ) {
                logger.warning("required attribute yUnits is missing, using dimensionless.");
            }
            if ( !element.hasAttribute("zUnits") ) {
                logger.warning("required attribute zUnits is missing, using dimensionless.");
            }
		}

		nitems = Integer.parseInt(element.getAttribute("nitems"));
		if(nitems < 1){
			throw new StreamException("yscan 'nitems' value is less than 1");
		}
		String yTagsText = element.getAttribute("yTags");
		if(yTagsText.length() > 0){ // 
			yTags = new double[nitems];
			String[] tokens = yTagsText.split("\\s*,\\s*");
			for(int i = 0; i < nitems; i++){
				yTags[i] = Double.parseDouble(tokens[i]);
			}
		}
		else{
			// See if yTagInterval is in use instead
			String sInterval = element.getAttribute("yTagInterval");
			if(sInterval.length() > 0){
				double rInterval = Double.parseDouble(sInterval);
				yTags = makeYtagsUsingInterval(element, nitems, rInterval);
			}
			else{
				// Just default to index entries
				yTags = new double[nitems];
				for(int i = 0; i < nitems; i++) 	yTags[i] = i;
			}
		}
		  
        String typeStr = element.getAttribute("type");
        DataTransferType type = DataTransferType.getByName(typeStr);
        transferType = type;
		  
	String yUnitsString = element.getAttribute("yUnits");
	if (yUnitsString != null) {
            yUnits = Units.lookupUnits(yUnitsString);
        }
	
        String zUnitsString = element.getAttribute("zUnits");
        if (zUnitsString != null) {
            zUnits = Units.lookupUnits(zUnitsString);
        }
		  
        String lname = element.getAttribute("name");
        if ( lname != null ) {
            this.name = lname;
        }
		  
		NodeList nl = element.getElementsByTagName("properties");
		logger.log(Level.FINER, "element y has {0} properties", nl.getLength());
		for (int i = 0; i < nl.getLength(); i++) {
			Element el = (Element) nl.item(i);
			Map<String,Object> m = StreamTool.processPropertiesElement(el);
				
			// make sure we don't conflict with the 6 reserved properites above
			for(String sPropName: m.keySet()){
				for(String sReserved: g_sCkAry){
					if(sPropName.equals(sReserved))
						throw new StreamException("Can't use reserved property name '"+
							sReserved + "inside a <yscan> plane properties element.");
				}
			}
			properties.putAll(m);
		}
	}
    
    private void processLegacyElement(Element element) {
        try {
            if ( !element.getTagName().equals("YScan") ) {
                throw new IllegalArgumentException("xml tree root node is not the right type. "+
                "Node type is: "+element.getTagName());
            }
            nitems= Integer.parseInt(element.getAttribute("nitems"));
            if ( element.getAttribute("yCoordinate") != null ) {
                String yCoordinateString= element.getAttribute("yCoordinate");
                yTags= new double[nitems];            
                int parseIdx=0;
                for (int i=0; i<nitems-1; i++) {                    
                    int toIdx= yCoordinateString.indexOf(",",parseIdx)-1;
                    yTags[i]= Double.parseDouble(yCoordinateString.substring(parseIdx,toIdx));
                    parseIdx= toIdx+2;
                }
                yTags[nitems-1]= Double.parseDouble(yCoordinateString.substring(parseIdx));
            } 
            String typeStr = element.getAttribute("type");
            DataTransferType type = DataTransferType.getByName(typeStr);
            transferType = type;
        } catch ( NumberFormatException ex ) {
            throw new IllegalArgumentException("Error in das2stream at yCoordinate");
        }
        name= element.getAttribute("name");
    }
    
    public StreamYScanDescriptor() {
    }
    
    public String getName() {
        return this.name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public double[] getYTags() {
        return (double[])yTags.clone();
    }
    
    public void setYCoordinates(double[] yCoordinates) {
        this.yTags = (double[])yCoordinates.clone();
        this.nitems = yCoordinates.length;
    }
    
    public int getNItems() {
        return nitems;
    }
    
    public Units getYUnits() {
        return yUnits;
    }
    
    public Units getZUnits() {
        return zUnits;
    }
    
    public void setZUnits(Units units) {
        zUnits = units;
    }
    
    public void setYCoordinates(DatumVector yCoords) {
        yUnits = yCoords.getUnits();
        yTags = yCoords.toDoubleArray(yUnits);
        nitems = yTags.length;
    }

    public void setDataTransferType(DataTransferType transferType) {
        this.transferType = transferType;
    }
    
    public DataTransferType getDataTransferType() {
        return transferType;
    }
    
	@Override
    public int getSizeBytes() {
        return nitems * transferType.getSizeBytes();
    }
    
    private double[] values;
    
	@Override
    public DatumVector read(ByteBuffer input) {
        if (values == null) {
            values = new double[nitems];
        }
        for (int i = 0; i < nitems; i++) {
            values[i] = transferType.read(input);
        }
        return DatumVector.newDatumVector(values, zUnits);
    }
    
	@Override
    public void write(DatumVector input, ByteBuffer output) {
        values = input.toDoubleArray(values, zUnits);
        for (int i = 0; i < nitems; i++) {
            transferType.write(values[i], output);
        }
    }
    
	@Override
    public Element getDOMElement(Document document) {
        Element element = document.createElement("yscan");
        element.setAttribute("nitems", String.valueOf(nitems));
        element.setAttribute("yTags", toString(yTags));
        element.setAttribute("yUnits", yUnits.toString());
        element.setAttribute("zUnits", zUnits.toString());
        element.setAttribute("type", transferType.toString());
        if ( name!=null && !name.equals("") ) element.setAttribute( "name", name );        
        return element;
    }
    
    private static String toString(double[] d) {
        StringBuilder buffer = new StringBuilder();
        buffer.append(d[0]);
        for (int i = 1; i < d.length; i++) {
            buffer.append(", ").append(d[i]);
        }
        return buffer.toString();
    }
    
    @Override
    public Object clone() {
        try {
            StreamYScanDescriptor clone = (StreamYScanDescriptor)super.clone();
            clone.yTags = (double[])this.yTags.clone();
            return clone;
        }
        catch (CloneNotSupportedException cnse) {
            throw new RuntimeException(cnse);
        }
    }
    
    @Override
    public String toString() {
        return "<yScan nitems="+nitems+">";
    }
    
	@Override
    public Object getProperty(String name) {
        return properties.get(name);
    }

	@Override
    public Map getProperties() {
        return new HashMap(properties);
    }

}