/*******************************************************************************
 * PSHDL is a library and (trans-)compiler for PSHDL input. It generates
 *     output suitable for implementation or simulation of it.
 *
 *     Copyright (C) 2013 Karsten Becker (feedback (at) pshdl (dot) org)
 *
 *     This program 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.
 *
 *     This program 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/>.
 *
 *     This License does not grant permission to use the trade names, trademarks,
 *     service marks, or product names of the Licensor, except as required for
 *     reasonable and customary use in describing the origin of the Work.
 *
 * Contributors:
 *     Karsten Becker - initial API and implementation
 ******************************************************************************/
package org.pshdl.interpreter.frames;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.pshdl.interpreter.FastSimpleInterpreter;
import org.pshdl.interpreter.FastSimpleInterpreter.LongAccess;
import org.pshdl.interpreter.FastSimpleInterpreter.LongAccess.RegUpdater;
import org.pshdl.interpreter.Frame;
import org.pshdl.interpreter.Frame.FastInstruction;

public class FastFrame {

	private final long stack[], constants[];
	public List<RegUpdater> regUpdates = new ArrayList<>();
	private int arrayPos = -1, bitPos = -1;
	private final int[] writeIndex = new int[8], bitIndex = new int[8];
	private final FastInstruction[] instructions;
	private final LongAccess[] internals, internals_prev;
	public final LongAccess[] outputAccess;
	public boolean disableEdge;

	public FastFrame(FastSimpleInterpreter fir, Frame f, boolean disableEdge) {
		this.stack = new long[f.maxStackDepth];
		this.constants = new long[f.constants.length];
		this.instructions = f.instructions;
		for (int i = 0; i < f.constants.length; i++) {
			final BigInteger bi = f.constants[i];
			constants[i] = bi.longValue();
		}
		this.internals = fir.internals;
		this.internals_prev = fir.internals_prev;
		this.outputAccess = new LongAccess[f.outputIds.length];
		for (int i = 0; i < f.outputIds.length; i++) {
			outputAccess[i] = internals[f.outputIds[i]];
		}
		this.disableEdge = disableEdge;
	}

	public boolean execute(int deltaCycle, int epsCycle) {
		int stackPos = -1;
		arrayPos = -1;
		bitPos = -1;
		long a = 0;
		long b = 0;
		regUpdates.clear();
		for (final FastInstruction fi : instructions) {
			if (fi.popA) {
				a = stack[stackPos--];
			}
			if (fi.popB) {
				b = stack[stackPos--];
			}
			switch (fi.inst) {
			case noop:
				break;
			case and:
				stack[++stackPos] = fixOp(b & a, fi.arg1);
				break;
			case arith_neg:
				stack[++stackPos] = fixOp(-a, fi.arg1);
				break;
			case bit_neg:
				stack[++stackPos] = fixOp(~a, fi.arg1);
				break;
			case bitAccessSingle:
				int bit = fi.arg1;
				if (bit == -1) {
					bit = bitIndex[bitPos];
					bitPos = -1;
				}
				long t = a >> bit;
				t &= 1;
				stack[++stackPos] = t;
				break;
			case bitAccessSingleRange:
				final int highBit = fi.arg1;
				final int lowBit = fi.arg2;
				long t2 = a >> lowBit;
				t2 &= (1l << ((highBit - lowBit) + 1)) - 1;
				stack[++stackPos] = t2;
				break;
			case cast_int:
				// Corner cases:
				// value is 0xF (-1 int<4>)
				// cast to int<8> result should be 0xFF
				// value is 0xA (-6 int<4>)
				// cast to int<3> result should be 0xE (-2)
				// Resize sign correctly to correct size
				final int shift = 64 - Math.min(fi.arg1, fi.arg2);
				stack[++stackPos] = ((a << shift) >> shift);
				break;
			case cast_uint:
				// There is nothing special about uints, so we just mask
				// them
				if (fi.arg1 != 64) {
					final long mask = (1l << (fi.arg1)) - 1;
					stack[++stackPos] = a & mask;
				} else {
					stack[++stackPos] = a;
				}
				break;
			case concat:
				stack[++stackPos] = (b << fi.arg2) | a;
				break;
			case const0:
				stack[++stackPos] = 0;
				break;
			case const1:
				stack[++stackPos] = 1;
				break;
			case const2:
				stack[++stackPos] = 2;
				break;
			case constAll1:
				final int width = fi.arg1;
				if (width == 64) {
					stack[++stackPos] = 0xFFFFFFFFFFFFFFFFl;
				} else {
					stack[++stackPos] = (1l << width) - 1;
				}
				break;
			case div:
				stack[++stackPos] = fixOp(b / a, fi.arg1);
				break;
			case eq:
				stack[++stackPos] = b == a ? 1 : 0;
				break;
			case greater:
				stack[++stackPos] = b > a ? 1 : 0;
				break;
			case greater_eq:
				stack[++stackPos] = b >= a ? 1 : 0;
				break;
			case less:
				stack[++stackPos] = b < a ? 1 : 0;
				break;
			case less_eq:
				stack[++stackPos] = b <= a ? 1 : 0;
				break;
			case loadConstant:
				stack[++stackPos] = constants[fi.arg1];
				break;
			case loadInternal:
				stack[++stackPos] = getInternal(fi.arg1, arrayPos).getDataLong();
				arrayPos = -1;
				break;
			case logiAnd:
				stack[++stackPos] = ((a != 0) && (b != 0)) ? 1 : 0;
				break;
			case logiOr:
				stack[++stackPos] = ((a != 0) || (b != 0)) ? 1 : 0;
				break;
			case logiNeg:
				stack[++stackPos] = a == 0 ? 1 : 0;
				break;
			case minus:
				stack[++stackPos] = fixOp(b - a, fi.arg1);
				break;
			case mul:
				stack[++stackPos] = fixOp(b * a, fi.arg1);
				break;
			case mod:
				stack[++stackPos] = fixOp(b % a, fi.arg1);
				break;
			case pow:
				stack[++stackPos] = fixOp(pow(b, a), fi.arg1);
				break;
			case not_eq:
				stack[++stackPos] = b != a ? 1 : 0;
				break;
			case or:
				stack[++stackPos] = fixOp(b | a, fi.arg1);
				break;
			case plus:
				stack[++stackPos] = fixOp(b + a, fi.arg1);
				break;
			case sll:
				stack[++stackPos] = fixOp(b << a, fi.arg1);
				break;
			case sra:
				stack[++stackPos] = fixOp(b >> a, fi.arg1);
				break;
			case srl:
				stack[++stackPos] = fixOp(b >>> a, fi.arg1);
				break;
			case xor:
				stack[++stackPos] = fixOp(b ^ a, fi.arg1);
				break;
			case isFallingEdge: {
				final int off = fi.arg1;
				final LongAccess access = getInternal(off, arrayPos);
				arrayPos = -1;
				if (access.skip(deltaCycle, epsCycle))
					return false;
				final long curr = access.getDataLong();
				if (!disableEdge) {
					final LongAccess prevAcc = internals_prev[off];
					prevAcc.offset = access.offset;
					final long prev = prevAcc.getDataLong();
					if ((prev != 1) || (curr != 0))
						return false;
				} else {
					if (curr != 0)
						return false;
				}
				access.setLastUpdate(deltaCycle, epsCycle);
				break;
			}
			case isRisingEdge: {
				final int off = fi.arg1;
				final LongAccess access = getInternal(off, arrayPos);
				arrayPos = -1;
				if (access.skip(deltaCycle, epsCycle))
					return false;
				final long curr = access.getDataLong();
				if (!disableEdge) {
					final LongAccess prevAcc = internals_prev[off];
					prevAcc.offset = access.offset;
					final long prev = prevAcc.getDataLong();
					if ((prev != 0) || (curr != 1))
						return false;
				} else {
					if (curr != 1)
						return false;
				}
				access.setLastUpdate(deltaCycle, epsCycle);
				break;
			}
			case posPredicate: {
				final int off = fi.arg1;
				final LongAccess access = getInternal(off, arrayPos);
				arrayPos = -1;
				// If data is not from this deltaCycle it was not
				// updated that means prior predicates failed
				if (!access.isFresh(deltaCycle, epsCycle))
					return false;
				if (access.getDataLong() == 0)
					return false;
				break;
			}
			case negPredicate: {
				final int off = fi.arg1;
				final LongAccess access = getInternal(off, arrayPos);
				arrayPos = -1;
				// If data is not from this deltaCycle it was not
				// updated that means prior predicates failed
				if (!access.isFresh(deltaCycle, epsCycle))
					return false;
				if (access.getDataLong() != 0)
					return false;
				break;
			}
			case pushAddIndex:
				if (fi.arg2 == 0) {
					writeIndex[++arrayPos] = (int) a;
				} else {
					bitIndex[++bitPos] = (int) a;
				}
				break;
			case writeInternal:
				final int off = fi.arg1;
				final LongAccess access = getInternal(off, -1);
				access.fillDataLong(arrayPos, writeIndex, a, deltaCycle, epsCycle);
				if (access.ii.isShadowReg) {
					regUpdates.add(access.getRegUpdater());
				}
				arrayPos = -1;
				break;
			}

		}
		for (final LongAccess longAccess : outputAccess) {
			if (arrayPos != -1) {
				longAccess.setOffset(writeIndex);
			}
			if (bitPos != -1) {
				longAccess.setBitOffset(bitIndex[bitPos]);
			}
			longAccess.setDataLong(stack[0], deltaCycle, epsCycle);
			if (longAccess.ii.isShadowReg) {
				regUpdates.add(longAccess.getRegUpdater());
			}
		}
		return true;
	}

	private long pow(long a, long n) {
		long x = 1;
		long nValue = n;
		while (nValue > 0) {
			if ((nValue % 2) == 0) {
				x = x * x;
			} else {
				x = a * x * x;
			}
			nValue /= 2;
		}
		return x;
	}

	private long fixOp(long value, int witdhWithType) {
		final int width = witdhWithType >> 1;
		if ((witdhWithType & 1) == 1)
			return ((value << width) >> width);
		return value & ((1l << width) - 1);
	}

	public LongAccess getInternal(int off, int arrayPos) {
		final LongAccess ea = internals[off];
		if (arrayPos != -1) {
			ea.setOffset(writeIndex);
		}
		if (bitPos != -1) {
			ea.setBitOffset(bitIndex[bitPos]);
		}
		return ea;
	}
}
