package accrue.analysis.visit;

import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.IntLit;
import polyglot.ast.NodeFactory;
import polyglot.frontend.Job;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.AscriptionVisitor;

public class InlineFinalConstants extends AscriptionVisitor {

    public InlineFinalConstants(Job job, TypeSystem ts, NodeFactory nf) {
        super(job, ts, nf);
    }

    @Override
    public Expr ascribe(Expr e, Type toType) throws SemanticException {
        if (e instanceof Field) {
            Field f = (Field) e;
            if (f.flags().isFinal()
                    && (f.type().isPrimitive() || ts.typeEquals(f.type(),
                                                                ts.String()))
                    && f.isConstant() && f.constantValueSet()) {
                Position p = f.position();
                Type t = f.type();
                if (t == null) {
                    throw new InternalCompilerError("null type!");
                }
                Object o = f.constantValue();
                if (o == null) {
                    return addCast(toType, nf.NullLit(p).type(ts.Null()));
                }
                else if (t.isBoolean()) {
                    return addCast(toType,
                                   nf.BooleanLit(p, (Boolean) o)
                                     .type(ts.Boolean()));
                }
                else if (ts.typeEquals(t, ts.String())) {
                    return nf.StringLit(p, o.toString()).type(ts.String());
                }
                else if (t.isChar()) {
                    char value =
                            ((o instanceof Character) ? (Character) o
                                    : (char) ((Number) o).intValue());
                    return addCast(toType, nf.CharLit(p, value).type(ts.Char()));
                }
                else if (t.isIntOrLess()) {
                    int value =
                            ((o instanceof Character) ? ((Character) o).charValue()
                                    : ((Number) o).intValue());
                    return addCast(toType, nf.IntLit(p, IntLit.INT, value)
                                             .type(ts.Int()));
                }
                else if (t.isLong()) {
                    long value =
                            ((o instanceof Character) ? ((Character) o).charValue()
                                    : ((Number) o).longValue());
                    return addCast(toType, nf.IntLit(p, IntLit.LONG, value)
                                             .type(ts.Long()));
                }
                // XXX Should we also handle floats?
            }
        }

        return e;
    }

    private Expr addCast(Type toType, Expr e) {
        if (toType.isNumeric() && !ts.typeEquals(toType, e.type())) {
            Position p = e.position();
            return nf.Cast(p, nf.CanonicalTypeNode(p, toType), e).type(toType);
        }
        return e;
    }
}
