/*
 * dex2jar - Tools to work with android .dex and java .class files
 * Copyright (c) 2009-2013 Panxiaobo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.dex2jar.ir.ts;

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

import com.googlecode.d2j.DexType;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.expr.Constant;
import com.googlecode.dex2jar.ir.expr.Exprs;
import com.googlecode.dex2jar.ir.expr.Local;
import com.googlecode.dex2jar.ir.expr.Value;
import com.googlecode.dex2jar.ir.expr.Value.VT;
import com.googlecode.dex2jar.ir.stmt.Stmt;
import com.googlecode.dex2jar.ir.stmt.Stmt.ST;
import com.googlecode.dex2jar.ir.stmt.Stmts;

/**
 * transforme IR to simple 3-addr format
 * 
 * a=b+c+d; => e=b+c; a=e+d;
 * 
 * @author bob
 * 
 */
public class JimpleTransformer implements Transformer {

    static class N {
        public List<Stmt> tmp;
        int nextIdx;
        private List<Local> locals;

        public N(List<Stmt> tmp, List<Local> locals) {
            super();
            this.tmp = tmp;
            this.locals = locals;
            nextIdx = locals.size();
        }

        Value newAssign(Value x) {
            Local loc = Exprs.nLocal(nextIdx++);
            loc.valueType = x.valueType;
            locals.add(loc);
            tmp.add(Stmts.nAssign(loc, x));
            return loc;
        }

    }

    @Override
    public void transform(IrMethod method) {
        List<Stmt> tmp = new ArrayList<>();
        N n = new N(tmp, method.locals);
        for (Stmt p = method.stmts.getFirst(); p != null; p = p.getNext()) {
            tmp.clear();
            convertStmt(p, n);
            for (Stmt t : tmp) {
                method.stmts.insertBefore(p, t);
            }

        }
    }

    private Value convertExpr(Value x, boolean keep, N tmp) {
        switch (x.et) {
        case E0:
            if (!keep) {
                switch (x.vt) {
                case CONSTANT:
                    Constant cst = (Constant) x;
                    if (cst.value instanceof String || cst.value instanceof DexType
                            || cst.value.getClass().isArray()) {
                        return tmp.newAssign(x);
                    }
                    break;
                case NEW:
                case STATIC_FIELD:
                    return tmp.newAssign(x);
                default:
                }
            }
            break;
        case E1:
            x.setOp(convertExpr(x.getOp(), false, tmp));
            if (!keep) {
                return tmp.newAssign(x);
            }
            break;
        case E2:
            x.setOp1(convertExpr(x.getOp1(), false, tmp));
            x.setOp2(convertExpr(x.getOp2(), false, tmp));
            if (!keep) {
                return tmp.newAssign(x);
            }
            break;
        case En:
            Value[] ops = x.getOps();
            for (int i = 0; i < ops.length; i++) {
                ops[i] = convertExpr(ops[i], false, tmp);
            }
            if (!keep) {
                return tmp.newAssign(x);
            }
            break;
        }

        return x;
    }

    private void convertStmt(Stmt p, N tmp) {
        switch (p.et) {
        case E0:
            return;
        case E1:
            boolean keep;
            switch (p.st) {
            case LOOKUP_SWITCH:
            case TABLE_SWITCH:
            case RETURN:
            case THROW:
                keep = false;
                break;
            default:
                keep = true;
                break;
            }
            p.setOp(convertExpr(p.getOp(), keep, tmp));
            break;
        case E2:
            if (p.st == ST.IDENTITY) {
                return;
            } else if (p.st == ST.FILL_ARRAY_DATA) {
                p.setOp1(convertExpr(p.getOp1(), false, tmp));
                p.setOp2(convertExpr(p.getOp2(), true, tmp));
            } else {
                p.setOp1(convertExpr(p.getOp1(), true, tmp));
                p.setOp2(convertExpr(p.getOp2(), p.getOp1().vt == VT.LOCAL, tmp));
            }
            break;
        case En:
            Value[] ops = p.getOps();
            for (int i = 0; i < ops.length; i++) {
                ops[i] = convertExpr(ops[i], true, tmp);
            }
            break;
        }
    }
}
