/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ExprEval.java
 *
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sun.electric.tool.simulation.interval;

import com.sun.electric.util.math.MutableInterval;

import java.util.LinkedList;

/**
 * This class contains a set of equation. They can be evaluated in different
 * ways - as point value, as interval, gradients will be added also.
 * If-then-else expression can be used to represent piecewise smooth functions,
 * which are defined in different regions by different analitical expressions.
 */

public class ExprEval {

    private LinkedList<Expr> el = new LinkedList<Expr>();
    private boolean printDetailed = false;
    boolean hasBoth = false;

    public void printAll( boolean detailed )
    {
		printDetailed = detailed;
		for(Expr e : el) {
			//if (bp->_opref <= 0 || !detailed && (bp->_opref <= 1 || bp->isConst())) continue;
			System.out.println(/*"[" + e._opref + "] " +*/ (e.name != null ? e.name : "_") + "." + e.id + " = " + e);
		}
		printDetailed = false;
    }

    public void calcAll()
    {
		for (Expr e : el) {
			//if (bp->_opref == 0) continue;
			e.calcVal();
			if (false) {
				System.out.println(/*"[" + e._opref + "] " +*/ (e.name != null ? e.name : "_") + "." + e.id + " = " + e + " = " + e.valString());
			}
		}
    }

    public boolean calcIntervalAll()
    {
		hasBoth = false;
		//setFpu( true );
		for (Expr e : el) {
			//if (bp->_opref == 0) continue;
			e.calcInterval();
			if (false) {
				System.out.println(/*"[" + e._opref + "] " +*/ (e.name != null ? e.name : "_") + "." + e.id + " = " + e + " = " + e.intervalString());
			}
		}
		//setFpu( false );
		return hasBoth;
    }

    abstract class Expr {
		private String name;
		private int id;
		Expr() {
			id = el.size();;
			el.add(this);
		}
		String name() { return name; };
		public void setName(String name) { this.name = name; }
		public String toString() { return toString(0); }
		abstract String toString(int prio);
		String refString(int prio) {
			return (name != null ? name : "_") + "." + id;
		}
		abstract void calcVal();
		abstract void calcInterval();
		abstract String valString();
		abstract String intervalString();
    }

    public abstract class DoubleExpr extends Expr {
		private double v;
		MutableInterval iv;
		DoubleExpr() {
			v = 0.0;
			iv = new MutableInterval();
		}

		public double v() { return v; }

		public void setV(double v) {
			this.v = v;
		}

		final MutableInterval iv() { return iv; }

		public double inf() { return iv.inf(); }

		public double sup() { return iv.sup(); }

		public DoubleExpr add(DoubleExpr augend ) {
			return new DoubleBinopExpr(this, DoubleBinopExpr.BOP_PLUS, augend);
		}

		public DoubleExpr subtract(DoubleExpr subtrahend ) {
			return new DoubleBinopExpr(this, DoubleBinopExpr.BOP_MINUS, subtrahend);
		}

		public DoubleExpr multiply(DoubleExpr multiplicand ) {
			return new DoubleBinopExpr(this, DoubleBinopExpr.BOP_TIMES, multiplicand);
		}

		public DoubleExpr divide(DoubleExpr divisor ) {
			return new DoubleBinopExpr(this, DoubleBinopExpr.BOP_SLASH, divisor);
		}

		public DoubleExpr negate() {
			return new DoubleUnopExpr(DoubleUnopExpr.UOP_NEG, this);
		}

		public DoubleExpr exp() {
			return new DoubleFunExpr(DoubleFunExpr.FUN1_EXP, this);
		}

		public DoubleExpr log() {
			return new DoubleFunExpr(DoubleFunExpr.FUN1_LOG, this);
		}

		public DoubleExpr abs() {
			return new DoubleFunExpr(DoubleFunExpr.FUN1_ABS, this);
		}

		public BooleanExpr lt(DoubleExpr e) {
			return new BooleanCompExpr(this, BooleanCompExpr.BOP_LT, e);
		}

		public BooleanExpr gt(DoubleExpr e) {
			return new BooleanCompExpr(this, BooleanCompExpr.BOP_GT, e);
		}

		public BooleanExpr le(DoubleExpr e) {
			return new BooleanCompExpr(this, BooleanCompExpr.BOP_LE, e);
		}

		public BooleanExpr ge(DoubleExpr e) {
			return new BooleanCompExpr(this, BooleanCompExpr.BOP_GE, e);
		}

		String valString() {
			return "" + v(); // ???
		}

		String intervalString() {
			return iv().toString();
		}
    }

    public class DoubleConstExpr extends DoubleExpr {
		DoubleConstExpr(double v) {
			setV(v);
			this.iv.assign(v);
		}
		public void setInterval(double lo, double hi) {
			this.iv.assign(lo, hi);
		}
		String toString(int prio) {
			return "" + v(); // ???
		}
		void calcVal() {}
		void calcInterval() {}
    }

    class DoubleBinopExpr extends DoubleExpr {
		static final int BOP_PLUS = 1;
		static final int BOP_MINUS = 2;
		static final int BOP_TIMES = 3;
		static final int BOP_SLASH = 4;
		private DoubleExpr e1;
		private int bop;
		private DoubleExpr e2;

		DoubleBinopExpr(DoubleExpr e1, int bop, DoubleExpr e2) {
			this.e1 = e1;
			this.bop = bop;
			this.e2 = e2;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			int pr = 0;

			switch( bop )
			{
				case BOP_PLUS:
					s = " + ";
					pr = 3;
					break;
				case BOP_MINUS:
					s = " - ";
					pr = 3;
					break;
				case BOP_TIMES:
					s = " * ";
					pr = 4;
					break;
				case BOP_SLASH:
					s = " / ";
					pr = 4;
					break;
			}

			s = e1.refString(pr) + s + e2.refString(pr+1);
			if (prio > pr) s = "( " + s + " )";
			return s;
		}
	
		void calcVal() {
			switch( bop )
			{
				case BOP_PLUS: setV( e1.v() + e2.v() ); break;
				case BOP_MINUS: setV( e1.v() - e2.v() ); break;
				case BOP_TIMES: setV( e1.v() * e2.v() ); break;
				case BOP_SLASH: setV( e1.v() / e2.v() ); break;
			}
		}

		void calcInterval() {
			switch (bop)
			{
				case BOP_PLUS:
					this.iv.assign(e1.iv()).add( e2.iv() );
					break;
				case BOP_MINUS:
					this.iv.assign(e1.iv()).sub( e2.iv() );
					break;
				case BOP_TIMES:
					this.iv.assign(e1.iv()).mul( e2.iv() );
					break;
				case BOP_SLASH:
					this.iv.assign(e1.iv()).div( e2.iv() );
					break;
			}
		}
    }

    class DoubleUnopExpr extends DoubleExpr {
		static final int UOP_NEG = 1;
		private int uop;
		private DoubleExpr e1;

		DoubleUnopExpr(int uop, DoubleExpr e1) {
			this.uop = uop;
			this.e1 = e1;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			switch( uop )
			{
				case UOP_NEG: s = "- "; break;
			}
			s = s + e1.refString(4);
			if (prio > 3) s = "( " + s + " )";
			return s;
		}

		void calcVal() {
			switch( uop )
			{
				case UOP_NEG: setV( - e1.v() ); break;
			}
		}

		void calcInterval() {
			switch (uop)
			{
				case UOP_NEG:
					this.iv.assign(e1.iv()).negate();
					break;
			}
		}
    }

    class DoubleFunExpr extends DoubleExpr {
		static final int FUN1_EXP = 1;
		static final int FUN1_LOG = 2;
		static final int FUN1_ABS = 3;
		private int fun;
		private DoubleExpr e1;

		DoubleFunExpr(int fun, DoubleExpr e1) {
			this.fun = fun;
			this.e1 = e1;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			switch( fun )
			{
				case FUN1_EXP: s = "exp( "; break;
				case FUN1_LOG: s = "log( "; break;
				case FUN1_ABS: s = "abs( "; break;
			}
			s = s + e1.refString(0) + " )";
			return s;
		}

		void calcVal() {
			switch( fun )
			{
				case FUN1_EXP: setV( Math.exp( e1.v() ) ); break;
				case FUN1_LOG: setV( Math.log( e1.v() ) ); break;
				case FUN1_ABS: setV( Math.abs( e1.v() ) ); break;
			}
		}

		void calcInterval() {
			MutableInterval a = e1.iv();
			switch (fun)
			{
				case FUN1_EXP:
					this.iv.assign(a).exp();
					break;
				case FUN1_LOG:
					this.iv.assign(a).log();
					break;
				case FUN1_ABS:
					this.iv.assign(a).abs();
					break;
			}
		}
    }

    class DoubleIteExpr extends DoubleExpr {
		private BooleanExpr cond;
		private DoubleExpr th;
		private DoubleExpr el;

		DoubleIteExpr(BooleanExpr cond, DoubleExpr th, DoubleExpr el) {
			this.cond = cond;
			this.th = th;
			this.el = el;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			s = cond.refString(1) + " ? " + th.refString(1) + " : " + el.refString(0);
			if (prio > 0) s = "( " + s + " )";
			return s;
		}

		void calcVal() {
			setV (cond.v() ? th.v() : el.v() );
		}

		void calcInterval() {
			MutableInterval t = th.iv();
			MutableInterval e = el.iv();
			if (cond.iboth())
			{
				this.iv.assign(t).interval_hull(e);
				hasBoth = true;
			} else if (cond.v())
				this.iv.assign(t);
			else
				this.iv.assign(e);
		}
    }

    public abstract class BooleanExpr extends Expr {
		private boolean v;
		private boolean iboth;
		final boolean v() { return v; }
		final void setV(boolean v) { this.v = v; }
		final boolean iboth() { return iboth; }
		final void setIBoth(boolean v) { this.iboth = v; }

		public DoubleExpr ite(DoubleExpr th, DoubleExpr el) {
			return new DoubleIteExpr(this, th, el);
		}

		BooleanExpr or(BooleanExpr be) {
			return new BooleanBinopExpr(this, BooleanBinopExpr.BOP_OR, be);
		}

		BooleanBinopExpr and(BooleanExpr be) {
			return new BooleanBinopExpr(this, BooleanBinopExpr.BOP_AND, be);
		}

		String valString() {
			return "" + v(); // ???
		}
		String intervalString() {
			return (iboth() ? "both" : valString());
		}
    }

    class BooleanBinopExpr extends BooleanExpr {
		static final int BOP_AND = 1;
		static final int BOP_OR = 2;
		private BooleanExpr e1;
		private int bop;
		private BooleanExpr e2;
	
		BooleanBinopExpr(BooleanExpr e1, int bop, BooleanExpr e2) {
			this.e1 = e1;
			this.bop = bop;
			this.e2 = e2;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			switch( bop )
			{
				case BOP_AND: s = " && "; break;
				case BOP_OR : s = " || "; break;
			}
			s = e1.refString(2) + s + e2.refString(2);
			if (prio > 1) s = "( " + s + ") ";
			return s;
		}

		void calcVal() {
			switch( bop )
			{
				case BOP_AND: setV( e1.v() && e2.v() ); break;
				case BOP_OR: setV( e1.v() || e2.v() ); break;
			}
		}

		void calcInterval() {
			switch( bop )
			{
				case BOP_AND:
					if (e1.iboth() && e2.iboth() ||
						e1.iboth() && e2.v() ||
						e2.iboth() && e1.v())
					{
						setIBoth( true );
					} else
					{
						setIBoth( false );
						setV( e1.v() && e2.v() );
					}
					break;
				case BOP_OR:
					if (e1.iboth() && e2.iboth() ||
						e1.iboth() && !e2.v() ||
						e2.iboth() && !e1.v())
					{
						setIBoth( true );
					} else
					{
						setIBoth( false );
						setV( e1.v() || e2.v() );
					}
					break;
			}
		}
    }

    class BooleanCompExpr extends BooleanExpr {
		static final int BOP_LT = 1;
		static final int BOP_GT = 2;
		static final int BOP_LE = 3;
		static final int BOP_GE = 4;
		private DoubleExpr e1;
		private int bop;
		private DoubleExpr e2;

		BooleanCompExpr(DoubleExpr e1, int bop, DoubleExpr e2) {
			this.e1 = e1;
			this.bop = bop;
			this.e2 = e2;
			calcVal();
		}

		String toString(int prio) {
			String s = null;
			switch( bop )
			{
				case BOP_LT: s = " < "; break;
				case BOP_GT: s = " > "; break;
				case BOP_LE: s = " <= "; break;
				case BOP_GE: s = " >= "; break;
			}
			s = e1.refString(3) + s + e2.refString(3);
			if (prio > 2) s = "( " + s + ") ";
			return s;
		}

		void calcVal() {
			switch( bop )
			{
				case BOP_LT: setV( e1.v() < e2.v() ); break;
				case BOP_GT: setV( e1.v() > e2.v() ); break;
				case BOP_LE: setV( e1.v() <= e2.v() ); break;
				case BOP_GE: setV( e1.v() >= e2.v() ); break;
			}
		}

		void calcInterval() {
			boolean c = false; /* certainly */
			boolean p = false; /* possibly */
			MutableInterval l = e1.iv();
			MutableInterval r = e2.iv();
			switch( bop )
			{
				case BOP_LT:
					c = l.clt(r);
					p = l.plt(r);
					break;
				case BOP_GT:
					c = l.cgt(r);
					p = l.pgt(r);
					break;
				case BOP_LE:
					c = l.cle(r);
					p = l.ple(r);
					break;
				case BOP_GE:
					c = l.cge(r);
					p = l.pge(r);
					break;
			}
			setIBoth( c != p );
			setV( c );
		}
    }

    /* Static methods, creating expressions */

    public DoubleConstExpr newConst() {
		return new DoubleConstExpr(0.0);
    }

    public DoubleConstExpr newConst( double v ) {
		return new DoubleConstExpr(v);
    }
}
