/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.main;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;

import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitEvent;
import com.cburch.logisim.circuit.CircuitListener;
import com.cburch.logisim.circuit.ReplacementMap;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.proj.Action;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.proj.ProjectEvent;
import com.cburch.logisim.proj.ProjectListener;
import com.cburch.logisim.tools.CustomHandles;

public class Selection extends SelectionBase {
	public static class Event {
		Object source;
		Event(Object source) { this.source = source; }
		public Object getSource() { return source; }
	}

	public static interface Listener {
		public void selectionChanged(Selection.Event event);
	}
	
	private class MyListener implements ProjectListener, CircuitListener {
		private WeakHashMap<Action,SelectionSave> savedSelections;
		
		MyListener() {
			savedSelections = new WeakHashMap<Action,SelectionSave>();
		}
		
		public void projectChanged(ProjectEvent event) {
			int type = event.getAction();
			if (type == ProjectEvent.ACTION_START) {
				SelectionSave save = SelectionSave.create(Selection.this);
				savedSelections.put((Action) event.getData(), save);
			} else if (type == ProjectEvent.ACTION_COMPLETE) {
				SelectionSave save = savedSelections.get(event.getData());
				if (save != null && save.isSame(Selection.this)) {
					savedSelections.remove(event.getData());
				}
			} else if (type == ProjectEvent.ACTION_MERGE) {
				SelectionSave save = savedSelections.get(event.getOldData());
				savedSelections.put((Action) event.getData(), save);
			} else if (type == ProjectEvent.UNDO_COMPLETE) {
				Circuit circ = event.getProject().getCurrentCircuit();
				Action act = (Action) event.getData();
				SelectionSave save = savedSelections.get(act);
				if (save != null) {
					lifted.clear();
					selected.clear();
					for (int i = 0; i < 2; i++) {
						Component[] cs;
						if (i == 0) cs = save.getFloatingComponents();
						else cs = save.getAnchoredComponents();

						if (cs != null) {
							for (Component c : cs) {
								if (circ.contains(c)) {
									selected.add(c);
								} else {
									lifted.add(c);
								}
							}
						}
					}
					fireSelectionChanged();
				}
			}
		}
		
		public void circuitChanged(CircuitEvent event) {
			if (event.getAction() == CircuitEvent.TRANSACTION_DONE) {
				Circuit circuit = event.getCircuit();
				ReplacementMap repl = event.getResult().getReplacementMap(circuit);
				boolean change = false;
				
				ArrayList<Component> oldAnchored;
				oldAnchored = new ArrayList<Component>(getComponents());
				for (Component comp : oldAnchored) {
					Collection<Component> replacedBy = repl.get(comp);
					if (replacedBy != null) {
						change = true;
						selected.remove(comp);
						lifted.remove(comp);
						for (Component add : replacedBy) {
							if (circuit.contains(add)) {
								selected.add(add);
							} else {
								lifted.add(add);
							}
						}
					}
				}
				
				if (change) {
					fireSelectionChanged();
				}
			}
		}       
	}

	private MyListener myListener;
	private boolean isVisible = true;
	private SelectionAttributes attrs;

	public Selection(Project proj, Canvas canvas) {
		super(proj);
		
		myListener = new MyListener();
		attrs = new SelectionAttributes(canvas, this);
		proj.addProjectListener(myListener);
		proj.addCircuitListener(myListener);
	}

	//
	// query methods
	//
	public boolean isEmpty() {
		return selected.isEmpty() && lifted.isEmpty();
	}
	
	public AttributeSet getAttributeSet() {
		return attrs;
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof Selection)) return false;
		Selection otherSelection = (Selection) other;
		return this.selected.equals(otherSelection.selected)
			&& this.lifted.equals(otherSelection.lifted);
	}

	public Set<Component> getComponents() {
		return unionSet;
	}
	
	public Collection<Component> getAnchoredComponents() {
		return selected;
	}
	
	public Collection<Component> getFloatingComponents() {
		return lifted;
	}

	public Collection<Component> getComponentsContaining(Location query) {
		HashSet<Component> ret = new HashSet<Component>();
		for (Component comp : unionSet) {
			if (comp.contains(query)) ret.add(comp);
		}
		return ret;
	}

	public Collection<Component> getComponentsContaining(Location query, Graphics g) {
		HashSet<Component> ret = new HashSet<Component>();
		for (Component comp : unionSet) {
			if (comp.contains(query, g)) ret.add(comp);
		}
		return ret;
	}

	public Collection<Component> getComponentsWithin(Bounds bds) {
		HashSet<Component> ret = new HashSet<Component>();
		for (Component comp : unionSet) {
			if (bds.contains(comp.getBounds())) ret.add(comp);
		}
		return ret;
	}

	public Collection<Component> getComponentsWithin(Bounds bds, Graphics g) {
		HashSet<Component> ret = new HashSet<Component>();
		for (Component comp : unionSet) {
			if (bds.contains(comp.getBounds(g))) ret.add(comp);
		}
		return ret;
	}

	public boolean contains(Component comp) {
		return unionSet.contains(comp);
	}

	//
	// graphics methods
	//
	public void draw(ComponentDrawContext context, Set<Component> hidden) {
		Graphics g = context.getGraphics();

		for (Component c : lifted) {
			if (!hidden.contains(c)) {
				Location loc = c.getLocation();
	
				Graphics g_new = g.create();
				context.setGraphics(g_new);
				c.getFactory().drawGhost(context, Color.GRAY,
						loc.getX(), loc.getY(), c.getAttributeSet());
				g_new.dispose();
			}
		}

		for (Component comp : unionSet) {
			if (!suppressHandles.contains(comp) && !hidden.contains(comp)) {
				Graphics g_new = g.create();
				context.setGraphics(g_new);
				CustomHandles handler
					= (CustomHandles) comp.getFeature(CustomHandles.class);
				if (handler == null) {
					context.drawHandles(comp);
				} else {
					handler.drawHandles(context);
				}
				g_new.dispose();
			}
		}

		context.setGraphics(g);
	}

	public void drawGhostsShifted(ComponentDrawContext context,
			int dx, int dy) {
		if (shouldSnap()) {
			dx = Canvas.snapXToGrid(dx);
			dy = Canvas.snapYToGrid(dy);
		}
		Graphics g = context.getGraphics();
		for (Component comp : unionSet) {
			AttributeSet attrs = comp.getAttributeSet();
			Location loc = comp.getLocation();
			int x = loc.getX() + dx;
			int y = loc.getY() + dy;
			context.setGraphics(g.create());
			comp.getFactory().drawGhost(context, Color.gray, x, y, attrs);
			context.getGraphics().dispose();
		}
		context.setGraphics(g);
	}
	
	@Override
	public void print() {
		System.err.println(" isVisible: " + isVisible); //OK
		super.print();
	}

}
