package adecWatt.model.unit;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
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.ScaledImage;
import misc.Story;
import static misc.ScaledImage.ONE_TILE;

import adecWatt.model.Acc;
import adecWatt.model.AdecWatt;
import adecWatt.model.Circuits;
import adecWatt.model.Comp;
import adecWatt.model.Editable;
import adecWatt.model.Item;
import adecWatt.model.Patch;
import adecWatt.model.Prop;
import adecWatt.model.Segm;
import adecWatt.model.Unit;
import adecWatt.model.xml.XmlComp;
import adecWatt.model.xml.XmlSegm;

@SuppressWarnings ("serial")
public abstract class Workspace extends Unit<Item<?, ?, ?>> {

    private float buildingOpacity = .5F;
    private float hiddenOpacity = .15F;
    public void		setOpacity (float buildingOpacity)	{ this.buildingOpacity = buildingOpacity; } // XXX update GUI
    public float	getOpacity ()				{ return buildingOpacity; }

    public Workspace (AdecWatt adecWatt, String id) {
	super (adecWatt, id);
    }

    // ========================================
    @SuppressWarnings ("unchecked")
    public void collectEmbedded () {
	if (xmlPermanent == null)
	    return;
	super.collectEmbedded ();
	ArrayList<Item> items = new ArrayList<Item> ();
	for (XmlSegm xmlSegm : xmlPermanent.getSegms ())
	    items.add (new Segm (this, xmlSegm));
	for (XmlComp xmlComp : xmlPermanent.getComps ())
	    items.add (new Comp (this, xmlComp));
	for (Item item : items)
	    item.fixName (true);
    }

    // ========================================
    public void updateView () {
	adecWatt.broadcastDisplay (AdecWatt.BroadcastUpdateWorkspace, this);
	updateTree ();
    }
    public void updateConnection () {
	adecWatt.broadcastDisplay (BroadcastConnection, this);
    }
    public void updateSelection (List<Item> items) {
	adecWatt.broadcastDisplay (BroadcastSetSelectionItems, this, items);
    }

    // ========================================
    public Story.Command getDoSelection (Item item) {
	return getDoSelection (Arrays.asList (item));
    }
    public Story.Command getDoSelection (final List<Item> items) {
	return story.new Command (StorySelectItem) {
		public void exec () { updateSelection (items); }
		public void undo () { }
	    };
    }
    public Story.Command getUndoSelection (Item item) {
	return getUndoSelection (Arrays.asList (item));
    }
    public Story.Command getUndoSelection (final List<Item> items) {
	return story.new Command (StorySelectItem) {
		public void exec () { }
		public void undo () { updateSelection (items); }
	    };
    }

    // ========================================
    public void storyAddComp (NonWorkspace model, Point2D.Double pos) {
	Comp comp = new Comp (this, model);
	if (comp.isSticky ())
	    return;
	storyAddItem (comp, pos);
    }
    public void storyAddSegm (NonWorkspace model, Point2D.Double pos) {
	Segm segm = new Segm (this, model);
	if (segm.isSticky ())
	    return;
	storyAddItem (segm, pos);
    }
    public void storyAddItem (Item item, Point2D.Double pos) {
	item.changePos (pos);
	Story.Commands commands = story.new Commands (StoryAddItem) {};
	commands.add (getUndoSelection ((List<Item>) null));
	item.validateContainer (commands, StoryAddItem);
	commands.add (getDoSelection (item));
	story.add (commands);
    }
    public void storyCloneItem (Item ref, Point2D.Double pos) {
	if (ref.isSticky ())
	    return;
	Item item = ref.clone (this);
	item.changePos (pos);
	Story.Commands commands = story.new Commands (StoryCloneItem) {};
	commands.add (getUndoSelection (ref));
	item.validateContainer (commands, StoryCloneItem);
	commands.add (getDoSelection (item));
	story.add (commands);
    }
    public void storyPasteItem (Vector<Item> sItem) {
	if (sItem == null)
	    return;
	Vector<Item> preSelection = new Vector<Item> (sItem.size ());
	for (Item ref : sItem) {
	    if (ref.isSticky ())
		continue;
	    preSelection.add (ref);
	}
	if (preSelection.size () < 1)
	    return;
	DimensionDouble size = getRealSize ();
	Story.Commands commands = story.new Commands (StoryPasteItem) {};
	Vector<Item> postSelection = new Vector<Item> (preSelection.size ());
	commands.add (getUndoSelection (preSelection));
	for (Item ref : preSelection) {
	    Item item = ref.clone (this);
	    Point2D.Double pos = item.getPos ();
	    // XXX si objet à la même place
	    //pos.x += .5; pos.y += .5;
	    item.changePos (Item.getPosInside (size, pos, item.getThetaDegree (), item.getSize ()));
	    postSelection.add (item);
	    item.validateContainer (commands, StoryPasteItem);
	}
	commands.add (getDoSelection (postSelection));
	story.add (commands);
    }
    public Item storyGetCloseItem (Story.Commands commands, Item item) {
	Item closeItem = getLocalEmbedded (item.getId ());
	if (closeItem != null)
	    return closeItem;
	closeItem = getCloseEmbedded (item);
	closeItem.validateContainer (commands, StoryLinkItem);
	return closeItem;
    }
    public void storyHideShowItem (List<Item> sItem, final boolean hidden) {
	if (sItem == null)
	    return;
	Story.Commands commands = story.new Commands (StoryHideItem) {};
	Vector<Item> toHideShow = new Vector<Item> (sItem.size ());
	for (Item ref : sItem) {
	    if (ref.isHidden () == hidden)
		continue;
	    toHideShow.add (storyGetCloseItem (commands, ref));
	}
	if (toHideShow.size () < 1)
	    return;
	commands.add (getUndoSelection (toHideShow));
	for (final Item item : toHideShow) {
	    commands.add (story.new Command (StoryHideItem) {
		    public void exec () { if (hidden) item.addModifier (Hidden); else item.removeModifier (Hidden); }
		    public void undo () { if (hidden) item.removeModifier (Hidden); else item.addModifier (Hidden); }
		    public void display () { item.updateView (); } // XXX utile ?
		});
	}
	commands.add (getDoSelection ((List<Item>) null));
	story.add (commands);
    }
    public void storyRemoveItems (List<Item> sItem) {
	if (sItem == null)
	    return;
	Vector<Item> toRemove = new Vector<Item> (sItem.size ());
	for (Item ref : sItem) {
	    Item localItem = getLocalEmbedded (ref.getId ());
	    if (localItem == null || localItem.isSticky ())
		continue;
	    toRemove.add (localItem);
	}
	if (toRemove.size () < 1)
	    return;
	Story.Commands commands = story.new Commands (StoryRemoveItem) {};
	commands.add (getUndoSelection (toRemove));
	for (final Item item : toRemove) {
	    commands.add (story.new Command (StoryRemoveItem) {
		    public void exec () { removeEmbedded (item); }
		    public void undo () { addEmbedded (item); }
		    public void displayExec () { item.updateView (BroadcastRemoveItem); }
		    public void displayUndo () { item.updateView (BroadcastNewItem); }
		});
	}
	commands.add (getDoSelection ((List<Item>) null));
	story.add (commands);
    }
    public Acc storyGetCloseAcc (Story.Commands commands, Item item, Accessory accessory) {
	Acc acc = (Acc) item.findEmbeddedRootOf (accessory);
	if (acc == null)
	    return null;
	Acc closeAcc = (Acc) item.getLocalEmbedded (acc.getId ());
	if (closeAcc != null)
	    return closeAcc;
	closeAcc = (Acc) item.getCloseEmbedded (acc);
	closeAcc.validateContainer (commands, StoryLinkAcc);
	return closeAcc;
    }
    public void storyAddAcc (Item item, final Accessory accessory) {
	Story.Commands commands = story.new Commands (StoryAddAcc) {};
	final Item localItem = storyGetCloseItem (commands, item);
	final Acc localAcc = storyGetCloseAcc (commands, localItem, accessory);
	if (localAcc == null)
	    return;
	final NonWorkspace oldAccessoryModel = localAcc.getModel ();
	if (accessory != oldAccessoryModel)
	    commands.add (story.new Command (StoryAddAcc) {
		    public void exec () {
			localAcc.setModel (accessory);
		    }
		    public void undo () {
			localAcc.setModel (oldAccessoryModel);
		    }
		    public void display () {
			localItem.updateView ();
		    }
		});
	story.add (commands);
    }
    public void storyRemoveAcc (Acc acc) {
	final Item localItem = getLocalEmbedded (acc.getContainer ().getId ());
	if (localItem == null)
	    return;
	final Acc oldAcc = (Acc) localItem.getLocalEmbedded (acc.getId ());
	if (oldAcc == null)
	    return;
	story.add (story.new Command (StoryRemoveAcc) {
		public void exec () { localItem.removeEmbedded (oldAcc); }
		public void undo () { localItem.addEmbedded (oldAcc); }
		public void display () { localItem.updateView (); }
	    });
    }
    public void storyConnect (Comp srcComp, Acc srcAcc, final String dstCompName, final String dstAccName) {
	Comp localComp = (Comp) getCloseEmbedded (srcComp);
	final Acc localAcc = localComp.getCloseEmbedded (srcAcc);
	Story.Commands commands = story.new Commands (StoryConnectAcc) {};
	localAcc.validateContainer (commands, StoryLinkAcc);

	final String oldConnectedTo = localAcc.getConnectedTo ();
	final Prop oldConnectedToProp = localAcc.getPropVal (Prop.PropConnectedTo);
	final String oldConnectedOn = localAcc.getConnectedOn ();
	final Prop oldConnectedOnProp = localAcc.getPropVal (Prop.PropConnectedOn);
	// XXX if (dstCompName == null) => remove
	// XXX if (dstAccName == null) => remove

	commands.add (story.new Command (StoryConnectAcc) {
		public void exec () {
		    localAcc.changeConnectedTo (dstCompName);
		    localAcc.changeConnectedOn (dstAccName);
		}
		public void undo () {
		    if (oldConnectedToProp == null)
			localAcc.removeOwnProp (Prop.PropConnectedTo);
		    else
			localAcc.changeConnectedTo (oldConnectedTo);
		    if (oldConnectedOnProp == null)
			localAcc.removeOwnProp (Prop.PropConnectedOn);
		    else
			localAcc.changeConnectedOn (oldConnectedOn);
		}
		public void display () {
		    localAcc.updateView ();
		    updateConnection ();
		}
	    });
	story.add (commands);
    }

    public void storyRotItem (Vector<Item> sItem, Vector<Double> thetas) {
	Story.Commands commands = story.new Commands (StoryRotItem) {};
	final DimensionDouble size = getRealSize ();
	int nbSelected = sItem.size ();
	for (int i = 0; i < nbSelected; i++) {
	    final Item item = sItem.get (i);
	    final double thetaDegree = thetas.get (i);
	    final double oldThetaDegree = item.getThetaDegree ();
	    if (thetaDegree == oldThetaDegree)
		continue;
	    final Item localItem = storyGetCloseItem (commands, item);
	    final Prop oldProp = item.getOwnProp (Prop.PropRot);
	    final Point2D.Double pos = item.getPos ();
	    commands.add (story.new Command (StoryRotItem) {
		    public void exec () {
			localItem.changeThetaDegree (thetaDegree);
			localItem.changePos (Item.getPosInside (size, pos, item.getThetaDegree (), item.getSize ()));
		    }
		    public void undo () {
			if (oldProp == null)
			    localItem.removeOwnProp (Prop.PropRot);
			else
			    localItem.changeThetaDegree (oldThetaDegree);
			localItem.changePos (pos);
		    }
		    public void display () {
			localItem.updateView (BroadcastChangePos);
			localItem.updateView (BroadcastChangeRot);
		    }
		});
	}
	story.add (commands);
    }

    public void storyMoveItem (Vector<Item> sItem, Vector<Point2D.Double> poss) {
	Story.Commands commands = story.new Commands (StoryMoveItem) {};
	DimensionDouble size = getRealSize ();
	int nbSelected = sItem.size ();
	for (int i = 0; i < nbSelected; i++) {
	    Item item = sItem.get (i);
	    final Point2D.Double insidePos = Item.getPosInside (size, poss.get (i), item.getThetaDegree (), item.getSize ());
	    final Point2D.Double old = item.getPos ();
	    if (insidePos.equals (old)) {
		item.updateView (BroadcastChangePos);
		continue;
	    }
	    final Item localItem = storyGetCloseItem (commands, item);
	    commands.add (story.new Command (StoryMoveItem) {
		    public void exec () { localItem.changePos (insidePos); }
		    public void undo () { localItem.changePos (old); }
		    public void display () { localItem.updateView (BroadcastChangePos); }
		});
	}
	story.add (commands);
    }

    public void storyRotResize (Item sItem, Point2D.Double sPos, final double sThetaDegree, final DimensionDouble sSize) {
	Story.Commands commands = story.new Commands (StoryRotResizeItem) {};
	final Item localItem = storyGetCloseItem (commands, sItem);
	DimensionDouble size = getRealSize ();
	final Point2D.Double oldPos = localItem.getPos ();
	final DimensionDouble oldSize = localItem.getSize ();
	final double oldThetaDegree = localItem.getThetaDegree ();
	final Point2D.Double insidePos = Item.getPosInside (size, sPos, sThetaDegree, sSize);
	commands.add (story.new Command (StoryRotResizeItem) {
		public void exec () { localItem.changeRotSize (insidePos, sThetaDegree, sSize); }
		public void undo () { localItem.changeRotSize (oldPos, oldThetaDegree, oldSize); }
		public void display () { localItem.updateView (BroadcastChangeRotSize); }
	    });
	story.add (commands);
    }

    // ========================================
    public Line2D.Double getMagnetPoint (Point2D.Double pos, Point2D.Double onGrid, double close) {
	double minD = close;
	Point2D.Double minPos = new Point2D.Double ();
	if (adecWatt.getHandleGlue ()) {
	    Point2D.Double closeHandle = (Point2D.Double) pos.clone ();
	    for (double[] bounds : allBounds)
		minD = Item.getCloseBound (bounds, pos, minD, closeHandle);
	    if (minD < close) {
		pos.x = closeHandle.x;
		pos.y = closeHandle.y;
		return new Line2D.Double (pos, pos);
	    }
	}
	if (adecWatt.getGridGlue ())
	    if (onGrid.distance (pos) < close) {
		pos.x = onGrid.x;
		pos.y = onGrid.y;
	    }
	if (adecWatt.getBoundGlue ()) {
	    boolean inSegment = adecWatt.getInSegmentGlue ();
	    double minL = close;
	    // XXX si croissement
	    //Vector<Line2D.Double> lines = new Vector<Line2D.Double> ();
	    Line2D.Double minLine = null;
	    for (double[] bounds : allBounds) {
		// XXX nombres magiques
		double oldX = bounds [6], oldY = bounds [7];
		for (int x = 0, y = 1; x < 8; x+=2, y+=2) {
		    Line2D.Double line = new Line2D.Double (oldX, oldY, oldX = bounds[x], oldY = bounds[y]);
		    // XXX si line=point null ?
		    double d = inSegment ? line.ptSegDist (pos) : line.ptLineDist (pos);
		    if (d < minL) {
			//lines.add (line);
			minLine = line;
			minL = d;
		    }
		}
	    }
	    // XXX et rester sur la grille ?
	    // if (lines.size () > 0) {
	    //     for (Line2D.Double line : lines) {
	    if (minLine != null) {
		double xDelta = minLine.x2 - minLine.x1;
		double yDelta = minLine.y2 - minLine.y1;
		if ((xDelta == 0) && (yDelta == 0))
		    // XXX vérif ci-dessus si line=point
		    return null;
		double u = ((pos.x - minLine.x1) * xDelta + (pos.y - minLine.y1) * yDelta) / (xDelta * xDelta + yDelta * yDelta);
		if (inSegment)
		    u = Math.min (1, Math.max (0, u));
		Point2D.Double result = new Point2D.Double (minLine.x1 + u*xDelta, minLine.y1 + u*yDelta);
		pos.x = result.x;
		pos.y = result.y;
		return minLine;
	    }
	}
	return null;
    }

    public void storySpaceItem (Vector<Item> items, boolean isHorizontal, Double spaceRequest) {
	if (items == null || items.size () < 2)
	    return;
	items.sort (isHorizontal ? Item.xPosComparator : Item.yPosComparator);
	double[][] axesValues = getAxesValues (items, isHorizontal);
	double minSpace = getMinSpace (axesValues[1]);
	int nbItems = items.size ();
	double space = spaceRequest != null ?
	    spaceRequest :
	    (axesValues[0][nbItems-1]-axesValues[0][0] - minSpace)/(nbItems-1);
	if (space < 0)
	    space = 0;
	for (int i = 1; i < nbItems; i++)
	    axesValues[0][i] = space+axesValues[0][i-1]+(axesValues[1][i-1]+axesValues[1][i])/2;
	storyMoveItem (items, getPos (items, isHorizontal, axesValues[0]));
    }
    public void storyDistributeItem (Vector<Item> items, boolean isHorizontal, Double distribRequest) {
	if (items == null || items.size () < 2)
	    return;
	items.sort (isHorizontal ? Item.xPosComparator : Item.yPosComparator);
	double[][] axesValues = getAxesValues (items, isHorizontal);
	int nbItems = items.size ();
	double distrib = distribRequest != null ?
	    distribRequest :
	    (axesValues[0][nbItems-1]-axesValues[0][0])/(nbItems-1);
	double minDistrib = getMinDistribution (axesValues[1]);
	if (distrib < minDistrib)
	    distrib = minDistrib;
	for (int i = 1; i < nbItems; i++)
	    axesValues[0][i] = axesValues[0][i-1]+distrib;
	storyMoveItem (items, getPos (items, isHorizontal, axesValues[0]));
    }

    static public enum Alignment {
	LEFT, CENTER, RIGHT, TOP, MIDDLE, BOTTOM;
    }

    public void storyAlignItem (Vector<Item> items, Alignment alignment) {
	if (items == null || items.size () < 2)
	    return;
	int nbItems = items.size ();
	Vector<Point2D.Double> poss = new  Vector<Point2D.Double> (nbItems);
	double x = 0, y = 0;
	switch (alignment) {
	case LEFT:
	    x = items.get (0).getPos ().x;
	    break;
	case TOP:
	    y = items.get (0).getPos ().y;
	    break;
	}
	for (Item item : items) {
	    Point2D.Double pos = item.getPos ();
	    DimensionDouble size = Item.getRotSize (item.getSize (), item.getThetaDegree ());
	    switch (alignment) {
	    case LEFT:
		x = Math.min (x, pos.x-size.width/2);
		break;
	    case CENTER:
		x += pos.x;
		break;
	    case RIGHT:
		x = Math.max (x, pos.x+size.width/2);
		break;
	    case TOP:
		y = Math.min (y, pos.y-size.height/2);
		break;
	    case MIDDLE:
		y += pos.y;
		break;
	    case BOTTOM:
		y = Math.max (y, pos.y+size.height/2);
		break;
	    }
	}
	switch (alignment) {
	case CENTER:
	    x /= nbItems;
	    break;
	case MIDDLE:
	    y /= nbItems;
	    break;
	}
	for (Item item : items) {
	    Point2D.Double pos = item.getPos ();
	    DimensionDouble size = Item.getRotSize (item.getSize (), item.getThetaDegree ());
	    switch (alignment) {
	    case LEFT:
		poss.add (new Point2D.Double (x+size.width/2, pos.y));
		break;
	    case CENTER:
		poss.add (new Point2D.Double (x, pos.y));
		break;
	    case RIGHT:
		poss.add (new Point2D.Double (x-size.width/2, pos.y));
		break;
	    case TOP:
		poss.add (new Point2D.Double (pos.x, y+size.height/2));
		break;
	    case MIDDLE:
		poss.add (new Point2D.Double (pos.x, y));
		break;
	    case BOTTOM:
		poss.add (new Point2D.Double (pos.x, y-size.height/2));
		break;
	    }
	}
	storyMoveItem (items, poss);
    }
    public double[][] getAxesValues (Vector<Item> items, boolean isHorizontal) {
	int nbItems = items.size ();
	double[][] result = new double [2][nbItems];
	int i = 0;
	for (Item item : items) {
	    Point2D.Double pos = item.getPos ();
	    DimensionDouble size = Item.getRotSize (item.getSize (), item.getThetaDegree ());
	    if (isHorizontal) {
		result[0][i] = pos.x;
		result[1][i] = size.width;
	    } else {
		result[0][i] = pos.y;
		result[1][i] = size.height;
	    }
	    i++;
	}
	return result;
    }
    public Vector<Point2D.Double> getPos (Vector<Item> items, boolean isHorizontal, double[] poss) {
	Vector<Point2D.Double> result = new Vector<Point2D.Double> ();
	int i = 0;
	for (Item item : items) {
	    Point2D.Double pos = item.getPos ();
	    result.add (isHorizontal ?
			new Point2D.Double (poss[i], pos.y) :
			new Point2D.Double (pos.x, poss[i]));
	    i++;
	}
	return result;
    }

    public double getMinDistribution (double [] widths) {
	double result = 0;
	for (int i = 1; i < widths.length; i++)
	    result = Math.max (result, widths[i-1]+widths[i]);
	return result/2;
    }
    public double getMinSpace (double [] widths) {
	double result = 0;
	for (int i = 0; i < widths.length; i++)
	    result += widths[i];
	result -= (widths[0]+widths[widths.length-1])/2;
	return result;
    }

    // ========================================
    public Element getXml (Node parent, Document document) {
	Element child = super.getXml (parent, document);
	return child;
    }

    // ========================================
    public Patch getPatch () {
	try {
	    Circuits circuits = Circuits.getCircuits (this, getAdecWatt ().getPermanentDB ().getPowerPlugId ());
	    return new Patch (circuits);
	} catch (Exception e) {
	    return null;
	}
    }

    // public boolean match (String text) {
    // 	try {
    // 	    if (super.match (text))
    // 		return true; 
    // 	    for (Item item : getAllItems ())
    // 		if (item.match (text))
    // 		// if (item.getName ().toLowerCase ().indexOf (text) >= 0 ||
    // 		//     item.getLocalName ().toLowerCase ().indexOf (text) >= 0 ||
    // 		//     item.getId ().indexOf (text) >= 0)
    // 		    return true;
    // 	} catch (Exception e) {
    // 	}
    // 	return false;
    // }

    // ========================================
    // XXX cache revoir acces
    protected HashSet<Comp> plugedComps = new HashSet<Comp> ();
    protected Hashtable<String, Item> namedItems = new Hashtable<String, Item> ();
    protected Vector<Item> allItems;
    protected Vector<double[]> allBounds;

    public Vector<Item> getAllItems ()		{ return allItems; }
    public void addPlugedComps (Comp comp)	{ plugedComps.add (comp); }
    public boolean containsItem (String itemId)	{ return namedItems.containsKey (itemId) || embedded.containsKey (itemId); }

    public void print (Graphics2D printGraphics, double lineWidth, Collection<String> itemsNotToPrint, boolean printCircuit) {
	Composite defaultComposit = printGraphics.getComposite ();
	Composite buildingComposit = ((AlphaComposite) defaultComposit).derive (buildingOpacity);
	Composite hiddenBuildingComposit = ((AlphaComposite) defaultComposit).derive (buildingOpacity*hiddenOpacity);
	Composite hiddenComposit = ((AlphaComposite) defaultComposit).derive (hiddenOpacity);
	printBlueprint (printGraphics);
	namedItems.clear ();
	plugedComps.clear ();
	for (Item item : getInheritedEmbeddedValues ())
	    namedItems.put (item.getId (), item);
	Building building = getBuilding ();
	Collection<Item<?, ?, ?>> buildingItems = null;
	if (building != null) {
	    buildingItems = building.getInheritedEmbeddedValues ();
	    for (Item item : buildingItems)
		namedItems.putIfAbsent (item.getId (), item);
	}
	allItems = new Vector<Item> (namedItems.values ());
	allItems.sort (Item.zPosComparator);
	Vector<Item> itemsUsed = new Vector<Item> ();
	for (Item item : allItems)
	    for (String accId : (TreeSet<String>) item.getEmbeddedIds ())
		try {
		    itemsUsed.add (namedItems.get (item.findEmbedded (accId).getConnectedTo ()));
		} catch (Exception e) {
		}
	String plugId = adecWatt.getPermanentDB ().getPowerPlugId ();
	allBounds = new Vector<double[]> (namedItems.size ());
	for (Item item : allItems) {
	    if (item.isReserved () || (itemsNotToPrint != null && itemsNotToPrint.contains (item.getId ())))
		continue;
	    boolean isBuilding = buildingItems != null && buildingItems.contains (item);
	    boolean isHidden = item.isHidden ();
	    if (!itemsUsed.contains (item)) {
		if (isHidden)
		    printGraphics.setComposite (isBuilding ? hiddenBuildingComposit : hiddenComposit);
		else if (isBuilding)
		    printGraphics.setComposite (buildingComposit);
	    }
	    try {
		allBounds.add (item.getBounds ());
		item.print (printGraphics, this, plugId);
	    } catch (Exception e) {
		System.err.println ("Can't print <"+item.getId ()+"> ("+item.getModel ()+")");
		// XXX supprimé dans l'héritage
		continue;
	    }
	    if (isBuilding||isHidden)
		printGraphics.setComposite (defaultComposit);
	}
	if (!printCircuit)
	    return;
	HashMap<Acc, Comp> linked = new HashMap<Acc, Comp> ();
	for (Comp beginComp : plugedComps)
	    for (Acc beginAcc : beginComp.allPlugs)
		try {
		    Comp endComp = (Comp) namedItems.get (beginAcc.getConnectedTo ());
		    if (endComp == null)
			continue;
		    linked.put (beginAcc, endComp);
		    Acc.printConnection (printGraphics, lineWidth,
					 beginComp.getAccCenter (beginAcc.getId ()),
					 endComp.getAccCenter (beginAcc.getConnectedOn ()));
		} catch (Exception e) {
		    //e.printStackTrace ();
		}
	Circuits circuits = new Circuits (linked, plugedComps);
	for (Comp plugedComp : plugedComps)
	    for (Acc acc : plugedComp.allPlugs) {
		Circuits.CircuitState circuitState = circuits.getState (plugedComp, acc);
		if (circuitState == null)
		    continue;
		Point2D c = plugedComp.getAccCenter (acc.getId ());
		// try {
		//     Comp endComp = (Comp) namedItems.get (acc.getConnectedTo ());
		//     Point2D a = plugedComp.getAccCenter (acc.getId ());
		//     Point2D b = endComp.getAccCenter (acc.getConnectedOn ());
		//     c = new Point2D.Double ((1*a.getX ()+b.getX ())/2, (1*a.getY ()+b.getY ())/2);
		// } catch (Exception e) {
		// }
		// XXX
		//Acc.printCircuit (printGraphics, lineWidth, c, circuitState);
		Acc.printCircuit (printGraphics, c, circuitState, .4);
	    }
    }

    public void printBlueprint (Graphics2D printGraphics) {
	ScaledImage scaledBlueprint = getBlueprint ();
	if (scaledBlueprint == null)
	    return;
	Double visible = getBlueprintVisibility ();
	if (visible != null && visible <= 0)
	    return;
	DimensionDouble blueprintSize = getBlueprintSize ();
	if (blueprintSize == null)
	    blueprintSize = getRealSize ();
	Point2D.Double blueprintPos = null;
	try {
	    blueprintPos = getBlueprintPos ();
	} catch (Exception e) {
	}
	if (blueprintPos == null)
	    blueprintPos = new Point2D.Double ();
	printImage (printGraphics, scaledBlueprint, blueprintPos, blueprintSize, 0., null);
    }

    public ArrayList<Item> findItems (Point2D.Double realPos, double close) {
	ArrayList<Item> result = new ArrayList<Item> ();
	double [] coord = new double [] {realPos.x, realPos.y};
	for (Item item : namedItems.values ()) {
	    coord[0] = realPos.x;
	    coord[1] = realPos.y;
	    if (item.containsClose (coord, close))
		result.add (item);
	}
	return result;
    }

    // ========================================
    static public void printImage (Graphics2D printGraphics, ScaledImage scaledImage,
				   Point2D realPos, DimensionDouble realSize, double thetaDegree, Double[] tileSize) {
	Point2D pos = new Point2D.Double (realPos.getX (), realPos.getY ());
	DimensionDouble size = new DimensionDouble (realSize.getWidth (), realSize.getHeight ());
	double theta = Math.toRadians (thetaDegree);
	Dimension tile = Item.getTile (tileSize, realSize);
	ScaledImage.ImageInfo imageInfo = scaledImage.newInfo2 (size, theta);
	BufferedImage reference = scaledImage.reference;
	if (pos != null)
	    printGraphics.translate (pos.getX ()+imageInfo.x, pos.getY ()+imageInfo.y);
	if (ONE_TILE.equals (tile))
	    printGraphics.drawImage (reference, imageInfo.at, null);
	else {
	    double width = reference.getWidth (), height = reference.getHeight ();
	    double scaleX = 1./tile.width, scaleY = 1./tile.height;
	    double stepX = width/tile.width, stepY = height/tile.height;
	    for (int i = 0; i < tile.width; i++)
		for (int j = 0; j < tile.height; j++) {
		    AffineTransform at = new AffineTransform (imageInfo.at);
		    at.translate (i*stepX, j*stepY);
		    at.scale (scaleX, scaleY);
		    printGraphics.drawImage (reference, at, null);
		}
	}
	if (pos != null)
	    printGraphics.translate (-pos.getX ()-imageInfo.x, -pos.getY ()-imageInfo.y);
    }

    static public void printText (Graphics2D printGraphics, String label, Color color, Point2D realPos, DimensionDouble realSize) {
	double minSide = Math.min (realSize.width, realSize.height);
	printText (printGraphics, label, color, realPos, new DimensionDouble (minSide, minSide), 0);
    }
    static public void printText (Graphics2D printGraphics, String label, Color color, Point2D realPos, DimensionDouble realSize, double thetaDegree) {
	if (label == null || "".equals (label))
	    return;
	AffineTransform af = printGraphics.getTransform ();
	
	Font labelFont = getLabelFont (label, printGraphics.getFont (), realSize, printGraphics);
	printGraphics.setFont (labelFont);
	Rectangle2D bounds = labelFont.getStringBounds (label, printGraphics.getFontRenderContext ());
	// XXX bug Java
	// FontMetrics fontMetrics = printGraphics.getFontMetrics (labelFont);
	// int ascent = fontMetrics.getMaxAscent ();
	// int descent = fontMetrics.getMaxDescent ();
	// double bottom = descent*bounds.getHeight ()/(ascent+descent);
	// System.err.println ("coucou font "+ascent+" "+descent+" =? "+bounds.getHeight ()+" "+bounds.getWidth ());
	double bottom = labelFont.getSize2D ()*0.201298701299;
	Point2D.Double pos = new Point2D.Double (realPos.getX (), //-bounds.getWidth ()/2,
						 realPos.getY ()); //-bounds.getHeight ()/2); //+labelFont.getSize2D ()/2-bottom);bounds
	printGraphics.translate (pos.getX (), pos.getY ());
	printGraphics.rotate (Math.toRadians (thetaDegree));
	printGraphics.setColor (new Color (1f, 1f, 1f, .8f));
	printGraphics.draw (new Rectangle2D.Double (bounds.getX () - bounds.getWidth ()/2, bounds.getY (),
						    bounds.getWidth (), bounds.getHeight ()));
	printGraphics.setColor (color);
	printGraphics.drawString (label, (float) (-bounds.getWidth ()/2), (float) (labelFont.getSize2D ()/2-bottom));
	printGraphics.setTransform (af);
    }

    static public Font getLabelFont (String labelText, Font labelFont, Dimension2D size, Graphics2D printGraphics) {
	if (labelText == null || labelText.isEmpty () || printGraphics == null)
	    return labelFont;
	float height = (float) size.getHeight ();//-labelSpace;
	labelFont = labelFont.deriveFont (height);
	int stringWidth = printGraphics.getFontMetrics (labelFont).stringWidth (labelText);
	float width = (float) size.getWidth ();//-labelSpace;
	float newFontSize = height * width / stringWidth;
	if (newFontSize < height)
	    labelFont = labelFont.deriveFont (newFontSize);
	return labelFont;
    }

    static public void printLine (Graphics2D printGraphics, Paint paint, Stroke stroke,
				  Point2D realPos, double length, double thetaDegree,
				  Stroke arrowStroke, Shape arrowStart, Shape arrowEnd) {
	Paint prevPaint = printGraphics.getPaint ();
	AffineTransform af = printGraphics.getTransform ();
	//Stroke prevStroke = printGraphics.getStroke ();

	Line2D line = new Line2D.Double (-length/2, 0, length/2, 0);
	printGraphics.setStroke (stroke);
	printGraphics.setPaint (paint);
	printGraphics.translate (realPos.getX (), realPos.getY ());
	printGraphics.rotate (Math.toRadians (thetaDegree));
	printGraphics.draw (line);
	printGraphics.setStroke (arrowStroke);
	//printGraphics.setPaint (java.awt.Color.blue);
	if (arrowStart != null) {
	    printGraphics.setTransform (af);
	    printGraphics.translate (realPos.getX (), realPos.getY ());
	    printGraphics.rotate (Math.toRadians (thetaDegree));
	    printGraphics.translate (-length/2, 0);
	    printGraphics.rotate (Math.toRadians (180));
	    printGraphics.fill (arrowStart);
	    printGraphics.draw (arrowStart);
	}
	//printGraphics.setPaint (java.awt.Color.red);
	if (arrowEnd != null) {
	    printGraphics.setTransform (af);
	    printGraphics.translate (realPos.getX (), realPos.getY ());
	    printGraphics.rotate (Math.toRadians (thetaDegree));
	    printGraphics.translate (length/2, 0);
	    printGraphics.fill (arrowEnd);
	    printGraphics.draw (arrowEnd);
	}

	printGraphics.setTransform (af);
	printGraphics.setPaint (prevPaint);
    }

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