/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * 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 net.morilib.dc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/07/14
 */
public class DcDefaultModel implements DcModel {

	private Map<Integer, List<Object>> registers;
	private BufferedReader stdrd;
	private List<Object> stack;
	private PrintStream stdout;
	private RoundingMode mode;
	private int scale;

	/**
	 * 
	 * @param in
	 * @param out
	 */
	public DcDefaultModel(Reader rd, PrintStream out) {
		registers = new HashMap<Integer, List<Object>>();
		stdout = out;
		stdrd  = new BufferedReader(rd);
		stack  = new ArrayList<Object>();
		scale  = 0;
		mode   = RoundingMode.HALF_UP;
	}

	/**
	 * 
	 * @param in
	 * @param out
	 */
	public DcDefaultModel(InputStream in, PrintStream out) {
		this(new InputStreamReader(in), out);
	}

	public void clear() {
		stack.clear();
	}

	public void duplicate() {
		stack.add(stack.get(stack.size() - 1));
	}

	public void exchange() {
		Object o, p;

		p = stack.remove(stack.size() - 1);
		o = stack.remove(stack.size() - 1);
		stack.add(p);
		stack.add(o);
	}

	public int getDepthOfStack() {
		return stack.size();
	}

	public int getInputRadix() {
		// TODO
//		return inputRadix;
		return 10;
	}

	public int getOutputRadix() {
		// TODO
//		return outputRadix;
		return 10;
	}

	public RoundingMode getRoundingMode() {
		return mode;
	}

	public int getScale() {
		return scale;
	}

	public Object peek(int c) {
		List<Object> l;
		Object o;

		if((l = registers.get(c)) == null || l.isEmpty()) {
			return BigDecimal.ZERO.setScale(0, mode);
		} else if(!((o = l.get(l.size() - 1)) instanceof BigDecimal)) {
			return o;
		} else if(((BigDecimal)o).scale() > scale) {
			return ((BigDecimal)o).setScale(scale, mode);
		} else {
			return o;
		}
	}

	public Object pop() {
		return stack.remove(stack.size() - 1);
	}

	public Object pop(int c) {
		List<Object> l;
		Object o;

		if((l = registers.get(c)) == null || l.isEmpty()) {
			throw new EmptyStackException();
		} else if(!((o = l.remove(l.size() - 1))
				instanceof BigDecimal)) {
			return o;
		} else if(((BigDecimal)o).scale() > scale) {
			return ((BigDecimal)o).setScale(scale, mode);
		} else {
			return o;
		}
	}

	public void print(Object o) {
		if(o instanceof BigDecimal) {
			stdout.print(
					((BigDecimal)o)
					.stripTrailingZeros()
					.toPlainString());
		} else {
			stdout.print(o.toString());
		}
	}

	public void printAsByte(Object o) throws IOException {
		BigDecimal d;

		if(o instanceof BigDecimal) {
			d = (BigDecimal)o;
			stdout.write(d.toBigInteger().toByteArray());
		} else {
			stdout.print(o.toString());
		}
	}

	public void println(Object o) {
		print(o);
		stdout.println();
	}

	public void printStackTrace() {
		for(int i = stack.size() - 1; i >= 0; i--) {
			print(stack.get(i));
		}
	}

	public void push(int c, Object o) {
		List<Object> l;

		if((l = registers.get(c)) == null) {
			l = new ArrayList<Object>();
			registers.put(c, l);
		}
		l.add(o);
	}

	public void pushConst(int n) {
		push(BigDecimal.valueOf(n).setScale(0, mode));
	}

	public void pushNumber(String s) {
		BigDecimal d;

		d = new BigDecimal(s);
		push(d.setScale(d.scale(), mode));
	}

	public String readLine() throws IOException {
		return stdrd.readLine();
	}

	public void set(int c, Object o) {
		List<Object> l;

		if((l = registers.get(c)) == null || l.isEmpty()) {
			l = new ArrayList<Object>();
			l.add(o);
			registers.put(c, l);
		} else {
			l.set(l.size() - 1, o);
		}
	}

	public void setInputRadix(int r) {
		// TODO
	}

	public void setOutputRadix(int r) {
		// TODO
	}

	public void setRoundingMode(RoundingMode m) {
		mode = m;
	}

	public void setScale(int r) {
		scale = r;
	}

	public void execShell(String cmd) {
		// TODO
	}

	public void push(Object o) {
		stack.add(o);
	}

	public boolean isEmpty() {
		return stack.size() == 0;
	}

}
