package adecWatt.model;

import java.awt.geom.Point2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import java.util.Vector;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import misc.DimensionDouble;
import misc.Util;

import adecWatt.model.xml.XmlProp;

public class Prop extends Permanent<XmlProp, Permanent.PropTypeEnum, Permanent.PropAttrEnum> {
    // ========================================
    static public PropTypeEnum toPropTypeEnum (String val) { return PropTypeEnum.valueOf (PropTypeEnum.class, Util.toCapital (val)); }

    static public final String
	PropAngle		= "angle",
	PropBegin		= "begin",
	PropBlueprint		= "blueprint",
	PropBlueprintPos	= "blueprintPos",
	PropBlueprintSize	= "blueprintSize",
	PropBlueprintVisibility	= "blueprintVisibility",
	PropBuilding		= "building",
	PropCircuit		= "circuit",
	PropColor		= "color",
	PropConnectedOn		= "connectedOn",
	PropConnectedTo		= "connectedTo",
	PropEnd			= "end",
	PropIcon		= "icon",
	PropLabelColor		= "labelColor",
	PropLabelProp		= "labelProp",
	PropLabelSize		= "labelSize",
	PropLength		= "length",
	PropLine		= "line",
	PropLowIcon		= "lowIcon",
	PropPos			= "pos",
	PropPoster		= "poster",
	PropRot			= "rot",
	PropSize		= "size",
	PropStyle		= "style",
	PropTileSize		= "tileSize",
	PropWatt		= "watt",
	PropWidth		= "width";

    static public final String	PropMix = "MIX";
    static public final Double	DoubleMix = Double.NaN;

    static public NumberFormat numberFormat = NumberFormat.getInstance (Locale.ENGLISH);
    static public NumberFormat geoFormat = NumberFormat.getInstance (Locale.ENGLISH);
    static {
	numberFormat.setMinimumFractionDigits (0);
	numberFormat.setGroupingUsed (false);
	geoFormat.setMinimumFractionDigits (6);
	geoFormat.setGroupingUsed (false);
    }

    // ========================================
    private PropTypeEnum type;
    public Integer rank;
    public String sValue;
    public Collection<String> enumChoice;
    public List<String> multiLabel;
    public boolean horizontalSpin;
    public Collection<String> unknownSpin;
    public int nbVal;
    public Double[] values;

    // ========================================
    public Collection<String>	getModifiersSet ()	{ return PropModifiersSet; }
    public PropTypeEnum		getTypeToken ()		{ return type; }
    public PropAttrEnum		getNameToken ()		{ return PropAttrEnum.Name; }
    public PropAttrEnum		getModifierToken ()	{ return PropAttrEnum.Modifier; }

    public void setType (PropTypeEnum type) {
	this.type = type;
	switch (type) {
	case Icon:
	case Image:
	case Enum:
	case Building:
	case Text:
	case Article:
	case City:
	    nbVal = 0;
	    break;
	case Number:
	    nbVal = 1;
	    break;
	case Geo:
	case Square:
	    nbVal = 2;
	    break;
	case Cube:
	    nbVal = 3;
	    break;
	default:
		System.err.println("coucou unknowed type:"+type);
		break;
	}
    }
    public String getEnumString () {
	if (enumChoice == null)
	    return "";
	return String.join ("|", enumChoice);
    }
    public String getLabelString () {
	if (multiLabel == null)
	    return "";
	return String.join ("|", multiLabel);
    }
    static public boolean isSValue (Prop prop) {
	return prop != null && prop.sValue != null && !prop.sValue.isEmpty ();
    }

    // ========================================
    public Prop (XmlProp xmlProp) {
	super (xmlProp);
	if (name == null)
	    throw new IllegalArgumentException ("Unknown XML unit has no name ("+xmlPermanent+").");
	setType (xmlProp.type);
	try {
	    rank = new Integer (Integer.parseInt (xmlProp.getFacet (PropAttrEnum.Rank)));
	} catch (Exception e) {
	}
	try {
	    unknownSpin = xmlProp.getSplitFacet (PropAttrEnum.Spin);
	    if (unknownSpin != null) {
		if (unknownSpin.contains (Permanent.HorizontalSpin)) {
		    horizontalSpin = true;
		    unknownSpin.remove (Permanent.HorizontalSpin);
		}
		if (unknownSpin.contains (Permanent.NoSpin)) {
		    horizontalSpin = false;
		    unknownSpin.remove (Permanent.NoSpin);
		}
		if (unknownSpin.size () < 1)
		    unknownSpin = null;
	    }
	    enumChoice = xmlProp.getSplitFacet (PropAttrEnum.Choice);
	    multiLabel = xmlProp.getOrderedSplitFacet (PropAttrEnum.Multilabel);
	    setValue (xmlProp.getValue ());
	} catch (Exception e) {
	    System.err.println ("XXX: "+getTypeToken ()+":"+name+":"+sValue);
	    e.printStackTrace ();
	}
    }

    public Prop (String name, PropTypeEnum type) {
	this (name, type, null, new TreeSet<String> (), null, null);
    }

    public Prop (String name, PropTypeEnum type, boolean horizontalSpin) {
	this (name, type);
	this.horizontalSpin = horizontalSpin;
    }

    public Prop (String name, PropTypeEnum type, Integer rank, Collection<String> modifiers, Collection<String> enumChoice, List<String> multiLabel) {
	this.name = name;
	setType (type);
	if (modifiers != null && modifiers.size () > 0)
	    this.modifiers = modifiers;
	this.rank = rank;
	this.enumChoice = enumChoice;
	this.multiLabel = multiLabel;
    }

    public Prop clone (boolean horizontalSpin) {
	Prop prop = clone ();
	prop.horizontalSpin = horizontalSpin;
	return prop;
    }
    public Prop clone (String sValue) {
	Prop prop = clone ();
	prop.setValue (sValue);
	return prop;
    }
    public Prop clone () {
	Prop result = new Prop (name, type);
	result.importFrom (this);
	return result;
    }
    protected void importFrom (Prop from) {
	super.importFrom (from);
	if (from.rank != null)
	    rank = from.rank;
	horizontalSpin = from.horizontalSpin;
	if (from.unknownSpin != null)
	    unknownSpin = from.unknownSpin;
	if (from.enumChoice != null && from.enumChoice.size () > 0)
	    enumChoice = new TreeSet<String> (from.enumChoice);
	if (from.multiLabel != null && from.multiLabel.size () > 0)
	    multiLabel = new Vector<String> (from.multiLabel);
	sValue = from.sValue;
	if (from.values != null)
	    values = Arrays.copyOf (from.values, from.values.length);
    }

    // ========================================
    static public Comparator<Prop> propComparator =
	new Comparator<Prop> () {
	public int compare (Prop o1, Prop o2) {
	    if (o1 == o2)
		return 0;
	    if (o1.rank != null && o2.rank != null)
		return o1.rank - o2.rank;
	    if (o1.rank == null && o2.rank == null)
		return o1.name.compareTo (o2.name);
	    return o1.rank == null ? 1 : -1;
	}
    };

    static public ArrayList<Prop> getOrderedProps (Collection<Prop> props) {
	ArrayList<Prop> result = new ArrayList<Prop> (props);
	result.sort (propComparator);
	return result;
    }

    // ========================================
    @SuppressWarnings ("unchecked")
    public Element getXml (Node parent, Document document) {
	Element child = super.getXml (parent, document);
	if (rank != null)
	    XmlProp.putFacet (child, PropAttrEnum.Rank, ""+rank);
	TreeSet<String> spin = horizontalSpin ? new TreeSet<String> () : null;
	if (horizontalSpin)
	    spin.add (Permanent.HorizontalSpin);
	XmlProp.putSplitFacet (child, PropAttrEnum.Spin, spin, unknownSpin);
	XmlProp.putSplitFacet (child, PropAttrEnum.Choice, enumChoice);
	XmlProp.putSplitFacet (child, PropAttrEnum.Multilabel, multiLabel);
	if (values != null)
	    // XXX devrait toujours êter synchrone
	    valuesChange ();
	XmlProp.putValue (child, sValue);
	return child;
    }
    public void valuesChange () {
	sValue = tab2string (values, nbVal);
    }
    public void sValueChange () {
	values = string2tab (sValue, nbVal);
    }
    public Prop getApplyMixProp (Prop src, Prop parent) {
	if (nbVal == 0 || sValue == null || sValue == PropMix || sValue.isEmpty () )
	    return this;
	Double[] tmp = new Double [nbVal];
	boolean noChange = true;
	for (int i = 0; i < nbVal; i++) {
	    if (values[i] == null)
		continue;
	    if (values[i] == DoubleMix) {
		noChange = false;
		if (src == null || src.values == null || src.values [i] == null ||
		    (parent != null && parent.values != null && src.values [i] == parent.values [i]))
		    continue;
		tmp[i] = src.values [i];
		continue;
	    }
	    if (parent != null && values [i] == parent.values [i]) {
		noChange = values [i] == null;
		continue;
	    }
	    tmp [i] = values [i];
	}
	boolean nullValue = true;
	for (int i = 0; i < nbVal; i++)
	    nullValue &= values[i] == null;
	if (nullValue)
	    return clone (null);
	if (noChange)
	    return this;
	return clone (tab2string (tmp, nbVal));
    }
    public final String tab2string (Double[] values, int nbVal) {
	String result = "", sep = "";
	if (values == null || nbVal < 1 || values == null)
	    return null;
	if (values.length != nbVal) {
	    System.err.println ("coucou problème de métamorphose : "+values.length+" != "+nbVal+"\n");
	    // XXX exception ?
	}
	boolean noVal = true;
	NumberFormat nf = type == PropTypeEnum.Geo ? geoFormat : numberFormat;
	for (Double val : values) {
	    result += sep;
	    if (val != null) {
		result += nf.format (val).replace (",", ".");
		noVal = false;
	    }
	    sep = "|";
	}
	if (noVal)
	    return null;
	return result;
    }
    static public final Double[] string2tab (String sValue, int nbVal) {
	if (nbVal < 1 || sValue == null || sValue == PropMix || sValue.isEmpty ())
	    return null;
	String[] tmp = sValue.split ("\\|");
	if (tmp.length < 1)
	    return null;
	if (tmp.length > nbVal) {
	    System.err.println ("coucou problème de métamorphose : "+tmp.length+" != "+nbVal+"\n");
	    // XXX exception ?
	}
	Double [] values = new Double [nbVal];
	for (int i = 0; i < nbVal && i < tmp.length; i++) {
	    if ("?".equals (tmp[i])) {
		values[i] = DoubleMix;
		continue;
	    }
	    if (tmp [i] != null && !tmp [i].isEmpty ())
		values[i] = Double.parseDouble (tmp [i].replace (",", "."));
	}
	return values;
    }

    // ========================================
    public void setValue (String value) {
	if (value == null || value.isEmpty ())
	    value = null;
	sValue = value;
	switch (getTypeToken ()) {
	case Icon:
	case Image:
	    // XXX charger l'image ?
	case Enum:
	    // XXX test dans l'ensemble des valeurs ?
	case Building:
	    // XXX test dans l'ensemble des valeurs ?
	case Text:
	case Article:
	case City:
	    break;
	case Number:
	case Geo:
	case Square:
	case Cube:
	    sValueChange ();
	    break;
	default:
		System.err.println ("coucou unknowed type:"+getTypeToken ());
		break;
	}
    }

    public void setThetaDegree (double thetaDegree) {
	if (nbVal != 3) {
	    System.err.println ("coucou Prop.setThetaDegree: not cube!");
	    // XXX exception ?
	}
	if (thetaDegree > 360 || thetaDegree < -360)
	    thetaDegree = thetaDegree - (((long)thetaDegree)/360)*360;
	try {
	    values [2] = thetaDegree;
	} catch (Exception e) {
	    values = new Double[] { null, null, thetaDegree }; // XXX
	}
	valuesChange ();
    }
    public void setAngle (double angle) {
	if (nbVal != 1) {
	    System.err.println ("coucou Prop.setAngle: not number!");
	    // XXX exception ?
	}
	try {
	    values [0] = angle;
	} catch (Exception e) {
	    values = new Double[] { angle }; // XXX
	}
	valuesChange ();
    }
    public void setLength (double length) {
	try {
	    values [0] = length;
	} catch (Exception e) {
	    values = new Double[] { length }; // XXX
	}
	valuesChange ();
    }

    // public double getLevel () {
    // 	if (nbVal != 3) {
    // 	    System.err.println ("coucou Prop.getLevel: not cube!");
    // 	    // XXX exception ?
    // 	}
    // 	return values [2];
    // }

    public void setSize (DimensionDouble size) {
	if (nbVal != 3) {
	    System.err.println ("coucou Prop.setSize: not cube!");
	    // XXX exception ?
	}
	try {
	    values [0] = size.getWidth ();
	    values [1] = size.getHeight ();
	} catch (Exception e) {
	    values = new Double[]{ size.getWidth (), size.getHeight (), null }; // XXX
	}
	valuesChange ();
    }
    public void setPosition (Point2D pos) {
	if (nbVal != 3) {
	    System.err.println ("coucou Prop.setPosition: not cube!");
	    // XXX exception ?
	}
	try {
	    values [0] = pos.getX ();
	    values [1] = pos.getY ();
	} catch (Exception e) {
	    values = new Double[]{ pos.getX (), pos.getY (), null }; // XXX
	}
	valuesChange ();
    }

    // ========================================
    public String toString () {
	return type+" "+name+" "+sValue+" "+rank;
    }

    // ========================================
}
