package com.baselet.diagram.io;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import com.baselet.control.basics.geom.Rectangle;
import com.baselet.control.enums.ElementId;
import com.baselet.diagram.DiagramHandler;
import com.baselet.diagram.DrawPanel;
import com.baselet.element.ElementFactorySwing;
import com.baselet.element.NewGridElement;
import com.baselet.element.facet.common.GroupFacet;
import com.baselet.element.interfaces.GridElement;
import com.baselet.element.old.custom.CustomElementCompiler;
import com.baselet.element.old.element.ErrorOccurred;
import com.baselet.gui.BaseGUI;
import com.baselet.gui.CurrentGui;
import com.baselet.gui.command.HelpPanelChanged;

/**
 * Describes what should happen with parsed elements from the input file
 * eg: set DiagramHandler variables, create GridElements etc.
 */
public class InputHandler extends DefaultHandler {

	private static final String[] oldGridElementPackages = new String[] { "com.baselet.element.old.element", "com.baselet.element.old.allinone", "com.baselet.element.old.custom" };

	private static final Logger log = LoggerFactory.getLogger(InputHandler.class);

	private DrawPanel _p = null;
	private GridElement e = null;
	private String elementtext;

	private int x;
	private int y;
	private int w;
	private int h;
	private String entityname;
	private String code;
	private String panel_attributes;
	private String additional_attributes;

	private String currentGroup;
	private final DiagramHandler handler;

	// to be backward compatible - add list of old elements that were removed so that they are ignored when loading old files
	private final List<String> ignoreElements;

	private String id; // Experimental elements have an id instead of an entityname

	public InputHandler(DiagramHandler handler) {
		this.handler = handler;
		_p = handler.getDrawPanel();
		ignoreElements = new ArrayList<String>();
		ignoreElements.add("main.control.Group");
		currentGroup = null;
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) {
		elementtext = "";
		if (qName.equals("element")) {
			panel_attributes = "";
			additional_attributes = "";
			code = null;
		}
		if (qName.equals("group")) { // TODO remove group-handling in InputHandler. Until UMLet v13, groups used own element-tags in XML. This has changed to the property group=x, so this handling is only for backwards compatibility
			currentGroup = handler.getDrawPanel().getSelector().getUnusedGroup();
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName) {
		String elementname = qName; // [UB]: we are not name-space aware, so use the qualified name

		if (elementname.equals("help_text")) {
			handler.setHelpText(elementtext);
			handler.getFontHandler().setDiagramDefaultFontSize(HelpPanelChanged.getFontsize(elementtext));
			handler.getFontHandler().setDiagramDefaultFontFamily(HelpPanelChanged.getFontfamily(elementtext));
			BaseGUI gui = CurrentGui.getInstance().getGui();
			if (gui != null && gui.getPropertyPane() != null) { // issue 244: in batchmode, a file can have a help_text but gui will be null
				gui.getPropertyPane().switchToNonElement(elementtext);
			}
		}
		else if (elementname.equals("zoom_level")) {
			if (handler != null) {
				handler.setGridSize(Integer.parseInt(elementtext));
			}
		}
		else if (elementname.equals("group")) {
			currentGroup = null;
		}
		else if (elementname.equals("element")) {
			if (id != null) {
				try {
					NewGridElement e = ElementFactorySwing.create(ElementId.valueOf(id), new Rectangle(x, y, w, h), panel_attributes, additional_attributes, handler);
					if (currentGroup != null) {
						e.setProperty(GroupFacet.KEY, currentGroup);
					}
					_p.addElement(e);
				} catch (Exception e) {
					log.error("Cannot instantiate element with id: " + id, e);
				}
				id = null;
			}
			else if (!ignoreElements.contains(entityname)) { // OldGridElement handling which can be removed as soon as all OldGridElements have been replaced
				try {
					if (code == null) {
						e = InputHandler.getOldGridElementFromPath(entityname);
					}
					else {
						e = CustomElementCompiler.getInstance().genEntity(code);
					}
				} catch (InstantiationException e1) {
					e = new ErrorOccurred();
				} catch (IllegalAccessException e1) {
					e = new ErrorOccurred();
				} catch (ClassNotFoundException e1) {
					e = new ErrorOccurred();
				}
				e.setRectangle(new Rectangle(x, y, w, h));
				e.setPanelAttributes(panel_attributes);
				e.setAdditionalAttributes(additional_attributes);
				handler.setHandlerAndInitListeners(e);

				if (currentGroup != null) {
					e.setProperty(GroupFacet.KEY, currentGroup);
				}
				_p.addElement(e);
			}
		}
		else if (elementname.equals("type")) {
			entityname = elementtext;
		}
		else if (elementname.equals("id")) { // new elements have an id
			id = elementtext;
		}
		else if (elementname.equals("x")) {
			Integer i = Integer.valueOf(elementtext);
			x = i.intValue();
		}
		else if (elementname.equals("y")) {
			Integer i = Integer.valueOf(elementtext);
			y = i.intValue();
		}
		else if (elementname.equals("w")) {
			Integer i = Integer.valueOf(elementtext);
			w = i.intValue();
		}
		else if (elementname.equals("h")) {
			Integer i = Integer.valueOf(elementtext);
			h = i.intValue();
		}
		else if (elementname.equals("panel_attributes")) {
			panel_attributes = elementtext;
		}
		else if (elementname.equals("additional_attributes")) {
			additional_attributes = elementtext;
		}
		else if (elementname.equals("custom_code")) {
			code = elementtext;
		}
	}

	@Override
	public void characters(char[] ch, int start, int length) {
		elementtext += new String(ch).substring(start, start + length);
	}

	private static GridElement getOldGridElementFromPath(String path) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		Class<?> foundClass = null;
		String className = path.substring(path.lastIndexOf("."));
		for (String possPackage : oldGridElementPackages) {
			try {
				foundClass = Thread.currentThread().getContextClassLoader().loadClass(possPackage + className);
				break;
			} catch (ClassNotFoundException e1) {/* do nothing; try next package */}
		}
		if (foundClass == null) {
			ClassNotFoundException ex = new ClassNotFoundException("class " + path + " not found");
			log.error(null, ex);
			throw ex;
		}
		else {
			return (GridElement) foundClass.newInstance();
		}
	}

}
