package org.yamcs.yarch.streamsql;


import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.codehaus.janino.SimpleCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.yarch.ColumnDefinition;
import org.yamcs.yarch.CompiledExpression;
import org.yamcs.yarch.DataType;
import org.yamcs.yarch.DbReaderStream;
import org.yamcs.yarch.TupleDefinition;
import org.yamcs.yarch.streamsql.StreamSqlException.ErrCode;

public abstract class Expression {
    protected DataType type=null;
    protected Expression[] children;
    protected TupleDefinition inputDef;

    protected boolean hasAggregates;
    protected boolean constant=false;
    String colName;
    static Logger log = LoggerFactory.getLogger(Expression.class);

    public Expression(Expression[] children) {
        this.children=children;
        hasAggregates=false;
        if(children!=null) {
            for(Expression c:children) {
                if(c.isAggregate() || c.hasAggregates) {
                    hasAggregates=true;
                }
            }
        }
        colName=String.format("%s0x%xd", this.getClass().getSimpleName(), this.hashCode());
    }
    protected boolean isAggregate() {return false;}


    final public boolean isConstant() {
        return constant;
    }


    /**
     * add a filter to the table if applicable and returns an expression where the condition is removed.
     * @param tableStream
     * @return
     * @throws StreamSqlException 
     */
    public Expression addFilter(DbReaderStream tableStream) throws StreamSqlException {
        // by default do nothing
        return this;
    }

    public void collectAggregates(List<AggregateExpression> list) {
        if(isAggregate()) 	list.add((AggregateExpression)this);
        else if(children!=null) {
            for(Expression c:children) {
                if(c.hasAggregates || c.isAggregate()) {
                    c.collectAggregates(list);
                }
            }
        }
    }


    protected abstract void doBind() throws StreamSqlException;
    public void bind(TupleDefinition inputDef2) throws StreamSqlException {
        this.inputDef=inputDef2;
        if(children!=null) {
            for(Expression c:children) {
                c.bind(inputDef);
            }
        }
        doBind();
    }


    public DataType getType() {
        return type;
    }

    protected void fillCode_Declarations(StringBuilder code) {
        //do nothing by default
    }
    protected void fillCode_Constructor(StringBuilder code) throws StreamSqlException {
        //do nothing by default
    }
    protected void fillCode_AllInputDefVars(StringBuilder code){
        for(ColumnDefinition cd:inputDef.getColumnDefinitions()) {
            if (cd.getType().val != DataType._type.PROTOBUF) {
                code.append("\t\t"+cd.getType().javaType()+" col"+cd.getName()+"=("+cd.getType().javaType()+")tuple.getColumn(\""+cd.getName()+"\");\n");
            }
        }
    }
    protected void fillCode_getValueBody(StringBuilder code)  throws StreamSqlException{};

    protected abstract void fillCode_getValueReturn(StringBuilder code)  throws StreamSqlException;



    private static AtomicInteger counter=new AtomicInteger();
    public CompiledExpression compile() throws StreamSqlException {
        String className = "Expression"+counter.incrementAndGet();
        StringBuilder source=new StringBuilder();
        source.append("package org.yamcs.yarch;\n")
        .append("public class "+className+" implements CompiledExpression {\n")
        .append("\tColumnDefinition cdef;\n");
        fillCode_Declarations(source);

        source.append("\tpublic "+className+"(ColumnDefinition cdef) {\n")
        .append("\t\tthis.cdef=cdef;\n");
        fillCode_Constructor(source);
        source.append("\t}\n");

        source.append("\tpublic Object getValue(Tuple tuple) {\n"); 
        if(!isConstant()) fillCode_AllInputDefVars(source);
        fillCode_getValueBody(source);

        // source.append("Value colid=t.getColumn(\"id\");\n");
        source.append("\n\t\treturn ");
        fillCode_getValueReturn(source);
        source.append(";\n");
        source.append("\t}\n")
        .append("\tpublic ColumnDefinition getDefinition() {\n")
        .append("\t\treturn cdef;\n")
        .append("\t}\n")
        .append("}\n");

        try {
            SimpleCompiler compiler=new SimpleCompiler();
            compiler.cook(new StringReader(source.toString()));
            @SuppressWarnings("unchecked")
            Class<CompiledExpression> cexprClass = (Class<CompiledExpression>) compiler.getClassLoader().loadClass("org.yamcs.yarch."+className);
            Constructor<CompiledExpression> cexprConstructor=cexprClass.getConstructor(ColumnDefinition.class);
            ColumnDefinition cdef=new ColumnDefinition(colName, type);
            return cexprConstructor.newInstance(cdef);
        } catch (Exception e) {
            log.warn("Got exception when compiling {} ", source.toString(), e);
            throw new StreamSqlException(ErrCode.COMPILE_ERROR, e.toString()); 
        }
    }


    /**
     * when the expression behaves like a column expression, this is the column name 
     * @return
     */
    public String getColName() {
        return colName;
    }
}

@@ -2978,16 +2978,154 @@
 ame()+%22=
+null;%5Cn%22)%0A                .append(%22%5Ct%5Ctif(tuple.hasColumn(%5C%22%22+cd.getName()+%22%5C%22)) %7B%5Cn%22)%0A                .append(%22%5Ct%5Ct%5Ctcol%22+cd.getName()+%22=
 (%22+cd.ge
@@ -3179,32 +3179,67 @@
 Name()+%22%5C%22);%5Cn%22)
+%0A                .append(%22%5Ct%5Ct%7D%5Cn%22)
 ;%0A            %7D%0A
@@ -4626,24 +4626,32 @@
 end(%22%7D%5Cn%22);%0A
+        
 %0A        try

Fix compiled expressions throwing errors on optional value columns