package at.oefai.aaa.animation;

import java.awt.Component;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import org.apache.batik.bridge.UpdateManager;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.script.Window;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.JSVGComponent;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGGElement;
import org.w3c.dom.svg.SVGSVGElement;

import at.oefai.aaa.agent.jam.Animator;
import at.oefai.aaa.agent.jam.types.Value;
import at.oefai.aaa.thread.IPausable;


/**
 * calls and registers animations in the necessary fashion.
 * (uses graphics update thread), encapsulates SVG Batik
 * @author Stefan Rank
 */
public class AnimationEngine implements IPausable, Animator {
    private static final SAXSVGDocumentFactory SVG_FACTORY =
        new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());

    private final JSVGCanvas svgCanvas = new JNoPackSVGCanvas(null, true, false);
    /** map for creating tasks by name */
    private final Map<String,BaseAnim> animMap = new HashMap<String,BaseAnim>();
    private final Map<String,File> soundMap = new HashMap<String,File>();

    private SVGDocument svgDoc = null;
    private UpdateManager updateManager = null;
    private Window scriptWindow = null;

    public AnimationEngine() {
        this.svgCanvas.setDocumentState(JSVGComponent.ALWAYS_DYNAMIC);
        this.svgCanvas.setEnableImageZoomInteractor(false);
        this.svgCanvas.setEnablePanInteractor(false);
        this.svgCanvas.setEnableResetTransformInteractor(false);
        this.svgCanvas.setEnableRotateInteractor(false);
        this.svgCanvas.setEnableZoomInteractor(false);

        // fill the animMap
        this.animMap.put("setAgentHasObject",new SetAgentHasObject());
        this.animMap.put("setOnto",new SetOnto());
        this.animMap.put("setPosition",new SetPosition());
        this.animMap.put("moveToPosition",new MoveToPosition());
        this.animMap.put("putDownObjectAtPosition",new PutDownObjectAtPosition());
        this.animMap.put("putObjectOntoObject",new PutObjectOntoObject());
        this.animMap.put("takeObjectAtPosition",new TakeObjectAtPosition());
        this.animMap.put("takeObjectFromObject",new TakeObjectFromObject());
        this.animMap.put("showExpression",new ShowExpression());
        this.animMap.put("useBombOnAgent",new UseBombOnAgent());
        this.animMap.put("useBombOnObject",new UseBombOnObject());
        this.animMap.put("useSwordOnAgent",new UseSwordOnAgent());
        this.animMap.put("useSwordOnObject",new UseSwordOnObject());
        this.animMap.put("useRopeOnAgent",new UseRopeOnAgent());
        this.animMap.put("useKeyOnObject",new UseKeyOnObject());
    }

    public void setDocument(final File doc) throws IOException {
        this.svgDoc = getSVGfromFile(doc);
    }

    public void showDocument() {
        this.svgCanvas.setSVGDocument(this.svgDoc);
    }

    public void registerListener(final ISvgListener listener) {
        /** add relevant listeners for events */
        // not needed: svgCanvas.addSVGDocumentLoaderListener();
        this.svgCanvas.addGVTTreeBuilderListener(listener);
        this.svgCanvas.addSVGLoadEventDispatcherListener(listener);
        this.svgCanvas.addGVTTreeRendererListener(listener);
        this.svgCanvas.addUpdateManagerListener(listener);
    }

    public Component getDisplayingComponent() {
        return this.svgCanvas;
    }

    public Animator.ITask animate(final String actor, final String anim, final List<Value> args) {
        assert this.scriptWindow != null : "no scriptwindow yet, cant do animation";
        BaseAnim ba = this.animMap.get(anim);
        if (ba != null) {
            ba = ba.newInstance();
            ba.init(this.svgDoc, actor, args);
            // retrieve sound if applicable
            ba.start(this.scriptWindow, getSound(actor,anim,args.get(0).toString()));
        } else { // didnt find a suitable animation
            assert false : "unknown animation (" + anim + ") requested";
        }
        return ba;
    }


    public void cacheUpdateManager() {
        this.updateManager = this.svgCanvas.getUpdateManager();
        if (this.updateManager != null) {
            // scripting window?
            this.scriptWindow = this.updateManager.getScriptingEnvironment().createWindow();
        }
    }

    public void registerSound(File f) {
        String name = f.getName();
        this.soundMap.put(name.substring(0,name.lastIndexOf('.')),f);
    }

    private File getSound(String actor, String anim, String firstArg) {
        // try to get the most specific soundfile for this animaction
        File f = this.soundMap.get(actor+anim+firstArg);
        if (f == null) {
            f = this.soundMap.get(actor+anim);
            if (f == null) {
                f = this.soundMap.get(anim+firstArg);
                if (f == null) {
                    f = this.soundMap.get(anim);
                }
            }
        }
        return f;
    }

    public void start(final CountDownLatch startSignal) {
        if (startSignal != null) { // may not be sensible to use a signal for the AnimEngine
            try {
                startSignal.await();
            } catch (InterruptedException e) {
                // ignore
            }
        }
        if (this.updateManager != null) {
            if (! this.updateManager.isRunning()) {
                this.updateManager.resume();
            }
        }
    }
    public void pause(final CountDownLatch returnSignal) {
        if (this.updateManager != null) {
            if (this.updateManager.isRunning()) {
                this.updateManager.suspend();
            }
        }
        if (returnSignal != null) {
            returnSignal.countDown();
        }
    }
    public boolean isPaused() {
        if (this.updateManager != null) {
            return ! this.updateManager.isRunning();
        }
        return true;
    }

    private SVGDocument getSVGfromFile(File svgFile) throws IOException {
        SVGDocument newSvg = null;
        URI svgURI = svgFile.toURI();
        newSvg = SVG_FACTORY.createSVGDocument(svgURI.toString());
        return newSvg;
    }

    public void appendAgentToDocument(File f, String name, int number) throws IOException {
        assert (this.svgDoc != null);
        SVGDocument newSvg = getSVGfromFile(f);
        // get the main svg element out of newsvg and append it with the id and suitable x,y to svgDoc
        SVGSVGElement toAppend = newSvg.getRootElement();
        toAppend = (SVGSVGElement) this.svgDoc.importNode(toAppend,true);
        toAppend.setId(name);
        toAppend.getX().getBaseVal().setValue(150.0f + number * 25.0f);
        toAppend.getY().getBaseVal().setValue(60.0f);
        // if there is a designated Agents group in the svg use it
        Element a = this.svgDoc.getElementById("Agents");
        if (a == null || !(a instanceof SVGGElement) ) {
            this.svgDoc.getRootElement().appendChild(toAppend);
        } else {
            a.appendChild(toAppend);
        }
    }

    public List<String> getObjectNames() {
        List<String> result = new ArrayList<String>();
        assert (this.svgDoc != null);
        Element a = this.svgDoc.getElementById("Objects");
        if (a != null && (a instanceof SVGGElement) ) {
            NodeList nl = a.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node element = nl.item(i);
                if (element instanceof SVGSVGElement) {
                    result.add(((SVGSVGElement) element).getAttribute("id"));
                }
            }
        }
        return result;
    }

    /**
     * remove all the previously registered sounds
     */
    public void clearSounds() {
        this.soundMap.clear();
    }

}
