package adecWatt.model;

import java.awt.Color;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import adecWatt.model.xml.XmlPermanent;
import adecWatt.model.unit.NonWorkspace;

public abstract class Embedded<P extends XmlPermanent<T, A>, T extends Enum<?>, A extends Enum<?>, C extends Editable<?,?,?,?>, E extends Embedded<?,?,?,?,?>>
    extends Editable<P, T, A, E> {

    static public final String globalPlace = "ean"; // ean = Everywhere and Nowhere

    // ========================================
    protected C container, containerRef;
    protected String placeId, id;
    protected NonWorkspace model;

    // ========================================
    public abstract A				getPlaceIdToken ();
    public abstract A				getModelToken ();
    public abstract Embedded<P,T,A,?,E>		getLink (Editable<?,?,?,?> container);

    public AdecWatt	getAdecWatt ()		{ return container.getAdecWatt (); }
    public String	getId ()		{ return id; }

    // public Unit<?>	getPermanent ()		{ return model; }
    public Unit<?>	getDirectUnit ()	{ return model; }
    public Unit<?>	getParentUnit ()	{ return model; }
    public NonWorkspace	getModel ()		{ return model; }
    public C		getContainer ()		{ return container; }

    public void	setModel (NonWorkspace model)	{ this.model = model; }

    @SuppressWarnings ("unchecked")
	public Embedded<P, T, A, C, E> getParent () {
	for (Editable<?,?,?,?> containerParent = container.getParent ();
	     containerParent != null;
	     containerParent = containerParent.getParent ()) {
	    Embedded<P, T, A, C, E> parent = (Embedded<P, T, A, C, E>) containerParent.getLocalEmbedded (id);
	    if (parent != null)
		return parent;
	}
	return null;
    }

    public void	setParentUnit (Unit<?> parent)	{ model = (NonWorkspace) parent; }

    // ========================================
    protected Embedded (C container, P xmlPermanent) {
	super (xmlPermanent);
	this.container = container;
	placeId = xmlPermanent.getFacet (getPlaceIdToken ());
	if (placeId == null)
	    placeId = container.getId ();
	if (name != null)
	    updateId ();
	String modelId = xmlPermanent.getFacet (getModelToken ());
	if (modelId == null)
	    throw new IllegalArgumentException ("Unknown XML embedded has no model ("+xmlPermanent+").");
	model = (NonWorkspace) getAdecWatt ().getPermanentDB ().getUnitById (modelId);
    }
    protected Embedded (String name, String placeId, C container, NonWorkspace model) {
	this.name = name == null ? container.getUniqName () : name;
	this.placeId = placeId == null ? container.getId () : placeId;
	this.container = container;
	this.model = model;
	updateId ();
    }
    protected void updateId () {
	id = placeId+":"+name;
    }
    @SuppressWarnings ("unchecked")
	public void fixName (boolean isLocal) {
	if (name == null) {
	    name = container.getUniqName ();
	    placeId = isLocal ? container.getId () : globalPlace;
	}
	updateId ();
	((Editable<?,?,?,Embedded<?,?,?,?,?>>) container).addEmbedded (this);
    }
    @SuppressWarnings ("unchecked")
	protected void importFrom (Embedded<P, T, A, ?, E> from) {
	for (Embedded<P, T, A, ?, E> current = from; current != null; current = current.getParent ())
	    super.importFrom (current);
	Editable<?,?,?,?> model = from.getModel ();
	if (model == null)
	    return;
	super.importFrom ((Editable<P, T, A, ?>) model);
    }
    @SuppressWarnings ("rawtypes")
	public abstract Embedded clone (C container);

    @SuppressWarnings ("rawtypes")
	protected Embedded clone () {
	throw new IllegalArgumentException ("Clone an Embedded forbidden");
    }

    // ========================================
    public Element getXml (Node parent, Document document) {
	Element child = super.getXml (parent, document);
	if (!container.getId ().equals (placeId))
	    XmlPermanent.putFacet (child, getPlaceIdToken (), placeId);
	// XXX model == null si pas trouvé au chargement exemple : comp de réserve
	XmlPermanent.putFacet (child, getModelToken (), model.getId ());
	return child;
    }

    public void getLocalLink (HashSet<Editable<?,?,?,?>> visited,
			      HashSet<Unit<?>> unitLinks, TreeSet<String> iconsLinks, TreeSet<String> imagesLinks) {
	if (visited.contains (this))
	    return;
	super.getLocalLink (visited, unitLinks, iconsLinks, imagesLinks);
	if (model != null)
	    model.getLocalLink (visited, unitLinks, iconsLinks, imagesLinks);
    }

    public boolean renameUnits (TreeMap<String, String> translateMap) {
	boolean result = model != null && translateMap.containsValue (model.getId ());
	if (placeId != null && translateMap.containsKey (placeId)) {
	    placeId = translateMap.get (placeId);
	    if (name != null)
		updateId ();
	    result = true;
	}
	return super.renameUnits (translateMap) || result;
    }

    // ========================================
    public Prop getLocalProp (String name, Prop.PropTypeEnum type) {
	// pas de type en paramètre mais rechercher dans model
	for (Prop prop : ownProps.values ())
	    if (name.equals (prop.name)) {
		// test type
		return prop;
	    }
	// Prop result = model.getProp (name);
	// result = (result != null) ? result.clone () : new Prop (name, type);
	Prop result = new Prop (name, type);
	ownProps.put (name, result);
	return result;
    }

    // ========================================
    // XXX getPropVal => super &&	!!! container.model.getEmbedded.getPropVal
    // XXX getPartialProp => super &&	!!! container.model.getEmbedded.getPartialProp
    // XXX getProp => super &&		!!! container.model.getEmbedded.getProp

    static public String labelPattern = "\\{\\{([^}]*)\\}\\}";
    public String getLabel () {
	try {
	    Pattern p = Pattern.compile (labelPattern);
	    String mString = getPropVal (Prop.PropLabelProp).sValue;
	    Matcher m = p.matcher (mString);
	    StringBuffer sb = new StringBuffer ();
	    while (m.find ()) {
		String key = mString.substring (m.start ()+2, m.end ()-2);
		String val = ""; //"{{"+key+"=?}}";
		try {
		    val = getPropVal (key).sValue;
		} catch (Exception e) {
		}
		m.appendReplacement (sb, val);
	    }
	    m.appendTail (sb);
	    return sb.toString ();
	} catch (Exception e) {
	    return null;
	}
    }
    public Color getLabelColor () {
	try {
	    return LPColor.getColor (getPropVal (Prop.PropLabelColor).sValue);
	} catch (Exception e) {
	    return Color.black;
	}
    }
    public double getLabelSize () {
	try {
	    return getPropVal (Prop.PropLabelSize).values[0];
	} catch (Exception e) {
	    return 0;
	}
    }
    public String getCircuit () {
	try {
	    String name = getPropVal (Prop.PropCircuit).sValue;
	    if (!name.isEmpty ())
		return name;
	} catch (Exception e) {
	}
	return null;
    }
    public double getWatt () {
	try {
	    return getPropVal (Prop.PropWatt).values[0];
	} catch (Exception e) {
	}
	return 0;
    }

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