//[Update:[Thu Sep 27 IST 2018]]
/*History:
**Fri Aug 17 2018
Started.
**Thu Aug 23 2018
  First working version.
**Sun Sep 02 2018
  Corrected a bug about time in playlists.
  Added 's' option to allow image sequences.
**Thu Sep 27 2018 (Comfort Inn, Gangtok)
  Corrected a seqeunce length bug (image sequences need to add 1 to "out" in playlist.
*/
import java.util.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;


public class Test  extends MLTBaseListener {
    int now;
    Hashtable<String, Chunk> chunks;
    Config currentConfig;
    Chunk currentChunk;
    int count, endsAt;
    HashMap<String,Integer> symtab;

    public void exitOnePar(MLTParser.OneParContext ctx) {
        //System.err.println("One par");
        ctx.value = new Vector<Par>();
        ctx.value.add(ctx.par().value);
    }

    public void exitMorePar(MLTParser.MoreParContext ctx) {
        //System.err.println("More par");
        ctx.pars().value.add(ctx.par().value);
        ctx.value = ctx.pars().value;
    }

    public void exitOneFilter(MLTParser.OneFilterContext ctx) {
        ctx.value = new Vector<Filter>();
        ctx.value.add(ctx.filter().value);
        //System.err.println("One filter: "+ctx);
    }

    public void exitMoreFilter(MLTParser.MoreFilterContext ctx) {
        //System.err.println("More filter: "+ctx.filters());
        //System.err.println(ctx.filter().value+", "+ ctx.filters().value);
        ctx.filters().value.add(ctx.filter().value);
        ctx.value = ctx.filters().value;
    }

    public void exitFilter(MLTParser.FilterContext ctx) {
        ctx.value = new Filter(ctx.ID().getText(),
                               ctx.pars().value);
    }

    public void exitNumPar(MLTParser.NumParContext ctx) {
        try {
            int val = Integer.parseInt(ctx.INT().getText());
            ctx.value = new Par(val);
        }
        catch(Exception ex) {ex.printStackTrace();}
    }
    
    public void exitFloatPar(MLTParser.FloatParContext ctx) {
        try {
            float val = Float.parseFloat(ctx.FLOAT().getText());
            ctx.value = new Par(val);
        }
        catch(Exception ex) {ex.printStackTrace();}
    }
    
    public void exitTextPar(MLTParser.TextParContext ctx) {
        try {
            ctx.value = new Par(ctx.TEXT().getText());
        }
        catch(Exception ex) {ex.printStackTrace();}
    }
    
    public void exitFormula(MLTParser.FormulaContext ctx) {
        symtab.put(ctx.ID().getText(), ctx.expr().value);
        //System.err.println(ctx.ID().getText()+" = "+ctx.expr().value);
        ctx.value = ctx.expr().value;
    }

    
    public void exitVar(MLTParser.VarContext ctx) {
        ctx.value = ctx.atom().value;
    }
    public void exitAdd(MLTParser.AddContext ctx) {
        
        ctx.value = ctx.expr().value+ctx.atom().value;
        //System.err.println(ctx.value+" = "+ctx.expr().value+" + "+ctx.atom().value);
    }
    
    public void exitSub(MLTParser.SubContext ctx) {
        ctx.value = ctx.expr().value-ctx.atom().value;
    }
    
    public void exitIdd(MLTParser.IddContext ctx) {
        ctx.value =  symtab.get(ctx.ID().getText());
    }
    
    public void exitNum(MLTParser.NumContext ctx) {
        ctx.value = -1;
        try {
            ctx.value= Integer.parseInt(ctx.INT().getText());
        }
        catch(Exception ex) {ex.printStackTrace();}
        
    }
    
    public void exitChunk(MLTParser.ChunkContext ctx) {
        String ty = ctx.type().getText();
        String name = ctx.ID().getText();
        String src = ctx.source().getText();
        int strt=0;
        try {
            strt = (ctx.start()==null ? 0 : Integer.parseInt(ctx.start().getText()));
        }
        catch(Exception ex) {ex.printStackTrace();}
        Vector<Filter> tmp = (ctx.filters()==null? null: ctx.filters().value);
        
        chunks.put(name, new Chunk(ty,name,src,strt,tmp,count));
        count++;
    }
    public void exitHead(MLTParser.HeadContext ctx) {
        try {
            now = ctx.timepoint().value;
            //System.err.println("now = "+now);
        }
        catch(Exception ex) {ex.printStackTrace();}
    }

    public void exitTimef(MLTParser.TimefContext ctx) {
        ctx.value = ctx.formula().value;
    }
    
    public void exitTimea(MLTParser.TimeaContext ctx) {
        ctx.value = ctx.atom().value;
    }
    
    public void exitEndSpec(MLTParser.EndSpecContext ctx) {
        endsAt = ctx.timepoint().value;
    }
    
    public void enterBody(MLTParser.BodyContext ctx) {
        String name = ctx.ID().getText();
        //System.err.print("Looking for chunk "+name);
        currentChunk = chunks.get(name);
        if(currentChunk==null) {
            throw new RuntimeException("Referring to non-existent chunk:["+name+"]");
        }
        else {
            //System.err.println("...got it!");
        }
        currentConfig = currentChunk.add(now);
    }

    public void exitBody(MLTParser.BodyContext ctx) {
        currentChunk.updateRiders();
    }
    
    public void exitSet(MLTParser.SetContext ctx) {
        String name = ctx.ID().getText();
        float val= ctx.number().value;
        currentConfig.set(name,val);
    }

    public void exitNumberint(MLTParser.NumberintContext ctx) {
        
        try {
            ctx.value = Integer.parseInt(ctx.INT().getText());
        }
        catch(Exception ex) {ex.printStackTrace();}
    }
    public void exitNumberflt(MLTParser.NumberfltContext ctx) {
        
        try {
            ctx.value = Float.parseFloat(ctx.FLOAT().getText());
        }
        catch(Exception ex) {ex.printStackTrace();}
    }
    
    public void exitOn(MLTParser.OnContext ctx) {
        String tmp = ctx.ID().getText();
        Chunk horse = chunks.get(tmp);
        horse.addRider(currentChunk);
    }
    
    public void exitOff(MLTParser.OffContext ctx) {
        String tmp = ctx.ID().getText();
        //System.err.println("\tGetting off "+tmp);
        Chunk horse = chunks.get(tmp);
        horse.removeRider(currentChunk);
    }
    
    public void exitIn(MLTParser.InContext ctx) {
        currentChunk.setVisible(true);
    }

    public void exitOut(MLTParser.OutContext ctx) {
        currentChunk.setVisible(false);
    }

    public Test() {
        chunks = new Hashtable<String, Chunk>();
        symtab = new HashMap<String, Integer>();
    }
    void dump() {
        /*
        chunks.get("red").dump();
        System.out.println("---------------");
        chunks.get("lsq").dump();
        System.out.println("---------------");
        */
        System.out.println("<mlt>\n<profile frame_rate_den=\"1\" frame_rate_num=\"30\"/>");
        System.out.println("<tractor>\n<multitrack><playlist>\n<producer length=\""+endsAt+"\">");
        System.out.println("<property name=\"mlt_service\">color</property>\n</producer>\n</playlist>");

        SortedSet<Chunk> allChunks = new TreeSet<Chunk>(chunks.values());
        for(Chunk c : allChunks) 
            c.emitPlaylist();
        System.out.println("</multitrack>");
        for(Chunk c : allChunks) 
            c.emitFilter();
        for(Chunk c : allChunks) 
            c.emitTransition();
        System.out.println("</tractor></mlt>");
    }
    
    public static void main(String args[]) throws Exception {
        MLTLexer lex = new MLTLexer(CharStreams.fromFileName(args[0]));
        CommonTokenStream tok = new CommonTokenStream(lex);
        MLTParser par = new MLTParser(tok);
        ParseTree tree = par.prog();
        ParseTreeWalker walker = new ParseTreeWalker();
        Test t = new Test();
        walker.walk(t,tree);
        t.dump();
    }
}

class Wndw {
    int start, end;
    
    Wndw(int s) {
        start = s;
    }
}

enum ChunkType {AUDIO, VIDEO, BOTH, PIP, DISTORT, SEQ; }

class Chunk implements Comparable<Chunk> {
    Vector<Config> list;
    Vector<Rider> r;
    String name;
    String src;
    int strt, count;
    Wndw window;
    Vector<Filter> fltrs;
    ChunkType type;
    
    Chunk(String ty, String n, String s, int shuru, Vector<Filter> f, int nambar) {
        if(ty.equals("a"))
            type = ChunkType.AUDIO;
        else if(ty.equals("v"))
            type = ChunkType.VIDEO;
        else if(ty.equals("b"))
            type = ChunkType.BOTH;
        else if(ty.equals("p"))
            type = ChunkType.PIP;
        else if(ty.equals("d"))
            type = ChunkType.DISTORT;
        else if(ty.equals("s"))
            type = ChunkType.SEQ;
        else
            throw new RuntimeException("Unknown Chunk type ["+ty+"]");
        
        list = new Vector<Config>();
        r = new Vector<Rider>();
        wList = new Vector<Wndw>();
        name = n;
        src = s;
        strt = shuru;
        fltrs = f;
        count = nambar;
        //System.err.println(name+": "+count);
        //showFilters();
    }

    private void showFilters() {
        if(fltrs==null) {
            System.err.println("No filter");
            return;
        }
        for(Filter f : fltrs)
            System.err.println(f);
    }
    public int compareTo(Chunk another) {
        return (count - another.count);
    }
    
    boolean equals(String n) {
        return name.equals(n);
    }
    
    void dump() {
        for(Config cfg : list)
            System.out.println(name+": "+cfg);
    }
    Vector<Wndw> wList;
    
    void setVisible(boolean show) {
        if(show) {
            window = new Wndw(now);
        }
        else {
            window.end = now-1;
            wList.add(window);
        }
    }
    
    private void property(String name, String what) {
        System.out.print("<property name=\""+name+"\">"+what+"</property>\n");
    }
    private void startTransition(int in, int out) {
        System.out.println("\n<transition in=\""+in+"\" out=\""+out+"\">");
    }
    private void endTransition() {
        System.out.println("</transition>");
    }
    private void startProducer(int in, int out) {
        System.out.println("\n<producer in=\""+in+"\" out=\""+out+"\">");
    }

    private void startProducer1(int in, int out) {
        System.out.println("\n<producer in=\""+in+"\" out=\""+out+"\" ttl=\"1\">");
    }

    private void endProducer() {
        System.out.println("</producer>");
    }
    

    private void w(String what) {
        System.out.print(what);
    }
    void emitPlaylist() {
        //System.err.println("Emitting playlist for ["+name+"]");
        w("<playlist>\n");
        int last = 0;
        for(Wndw wn : wList) {
            if(wn.start>0) w("<blank length=\""+(wn.start-last-1)+"\"/>\n");
            if(type==ChunkType.SEQ)
                startProducer1(strt,strt+wn.end-wn.start+1);
            else
                startProducer(strt,strt+wn.end-wn.start);
            property("resource",src);
            endProducer();
            last = wn.end;
        }
        w("</playlist>\n");
    }

    void emitFilter() {
        if(type==ChunkType.VIDEO) {
            System.out.println("<filter gain=\"0\"  mlt_service=\"volume\" track=\""+(count+1)+"\"/>");
        }
        if(fltrs==null) return;
        for(Filter f : fltrs)
            f.emit(count+1);

    }

    void emitTransition() {
        //System.err.println("Emitting transition for ["+name+"]");
        Config last = null;
        boolean first = true;
        for(Config cfg : list) {
            if(first) {
                first = false;
                last = cfg;
                continue;
            }
            System.out.println();
            startTransition(last.getTime(),cfg.getTime());
            property("mlt_service","composite");
            property("b_track",""+(count+1));
            
            property("geometry","0="+last.geom()+"; -1="+cfg.geom()+";");
            if(type==ChunkType.DISTORT) {
                property("distort","1");
            }
            property("fill","1");
            endTransition();

            if(type==ChunkType.AUDIO || type==ChunkType.BOTH) {
                startTransition(last.getTime(),cfg.getTime());
                property("mlt_service","mix");
                property("b_track",""+(count+2));
                property("combine","1");
                endTransition();
            }
            last = cfg;
        }
    }

    int now;
    Config add(int time) {
        now = time;
        Config naya = (list.isEmpty()? new Config() :
                       list.lastElement().copy(time));
        list.add(naya);
        return naya;
    }

    float lastX() {
        return list.lastElement().get("x");
    }
    
    float lastY() {
        return list.lastElement().get("y");
    }
    
    void addRider(Chunk man) {
        float dx = man.lastX()-lastX();
        float dy = man.lastY()-lastY();
        r.add(new Rider(dx,dy,man));
    }
    
    void removeRider(Chunk man) {
        Rider toRemove = null;
        for(Rider rd: r) 
            if(rd.c.equals(man)) {
                toRemove = rd;
                break;
            }
        r.remove(toRemove);
    }
    
    void updateRiders() {
        for(Rider rider : r)
            rider.addConfig(list.lastElement());
    }
}
class Config {
    HashMap<String,Float> vals;
    int time;
    Config() {
        vals = new HashMap<String,Float>();
        vals.put("x",0f);
        vals.put("y",0f);
        vals.put("w",100f);
        vals.put("h",100f);
        vals.put("a",100f);
    }

    void setTime(int t) {
        time = t;
    }
    
    int getTime() {
        return time;
    }
    
    void set(String name, float value) {
        vals.put(name,value);
    }

    float get(String name) {
        return vals.get(name);
    }
    Config copy(int t) {
        Config tmp = new Config();
        tmp.set("x",get("x"));
        tmp.set("y",get("y"));
        tmp.set("w",get("w"));
        tmp.set("h",get("h"));
        tmp.set("a",get("a"));
        tmp.setTime(t);
        return tmp;
    }
    public String toString() {
        return "["+
            "time="+time+", "+
            "x="+vals.get("x")+", "+
            "y="+vals.get("y")+", "+
            "w="+vals.get("w")+", "+
            "h="+vals.get("h")+", "+
            "a="+vals.get("a")+"]";
    }
    public String geom() {
        return 
            vals.get("x")+"%,"+vals.get("y")+"%:"+
            vals.get("w")+"%x"+vals.get("h")+"%:"+vals.get("a");
    }
}

    
class Rider {

    Chunk c;
    float dx, dy;
    Rider(float x, float y, Chunk ch) {
        dx = x;
        dy = y;
        c = ch;
    }
    
    void addConfig(Config cfg) {
        Config temp = c.add(cfg.getTime());
        temp.set("x",cfg.get("x")+dx);
        temp.set("y",cfg.get("y")+dy);
    }
}

enum Type {INT,TEXT, FLOAT; }

class Par {
    int num;
    float flt;
    String txt;
    Type type;

    Par(int v) {
        num = v;
        type = Type.INT;
    }
    
    Par(float v) {
        flt = v;
        type = Type.FLOAT;
    }
    
    Par(String v) {
        txt = v;
        type = Type.TEXT;
    }

    public String toString() {
        switch(type) {
        case INT:
            return "int:"+num;
        case FLOAT:
            return "float:"+flt;
        case TEXT:
            return "text:"+txt;
        }
        return null;
    }
}

class Filter {
    String name;
    Vector<Par> pars;

    Filter(String n,Vector<Par> p) {
        name = n;
        pars = p;
    }

    public String toString() {
        String str =  "[Filter["+name+"(";
        for(Par p : pars)
            str += p+", ";
        str += ")]]";
        return str;
    }

    void emit(int track) {
        if(name.equals("gain")) {
            System.out.println("<filter gain=\""+pars.get(0).flt+"\"  mlt_service=\"volume\" track=\""+track+"\"/>");
            return;
        }

        if(name.equals("chroma")) {
            System.out.println("<filter  mlt_service=\"chroma\"  track=\""+track+"\">");
            System.out.println("<property name=\"key\">"+pars.get(0).txt+"</property>");
            System.out.println("<property name=\"variance\">"+pars.get(1).flt+"</property>");
            System.out.println("</filter>");
            return;
        }

        throw new RuntimeException("Unknown filter: ["+name+"]");
        
    }
}
