/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 tests.util;

import java.util.Stack;

/**
 * A stack to store the parameters of a call, as well as the call stack.
 *
 */
public class CallVerificationStack extends Stack<Object> {

    /*
     * --------------------------------------------------------------------
     * Class variables
     * --------------------------------------------------------------------
     */

    private static final long serialVersionUID = 1L;

    // the singleton
    private static final CallVerificationStack _instance = new CallVerificationStack();

    /*
     * --------------------------------------------------------------------
     * Instance variables
     * --------------------------------------------------------------------
     */

    // the call stack, store StackTraceElement
    private final Stack<StackTraceElement> callStack = new Stack<StackTraceElement>();

    /*
     * -------------------------------------------------------------------
     * Constructors
     * -------------------------------------------------------------------
     */

    /**
     * Can't be instantiated.
     */
    private CallVerificationStack() {
        // empty
    }

    /*
     * -------------------------------------------------------------------
     * Methods
     * -------------------------------------------------------------------
     */

    /**
     * Gets the singleton instance.
     *
     * @return the singleton instance
     */
    public static CallVerificationStack getInstance() {
        return _instance;
    }

    /**
     * Pushes the call stack.
     */
    private void pushCallStack() {
        StackTraceElement[] eles = (new Throwable()).getStackTrace();
        int i;
        for (i = 1; i < eles.length; i++) {
            if (!eles[i].getClassName().equals(this.getClass().getName())) {
                break;
            }
        }
        this.callStack.push(eles[i]);
    }

    /**
     * Gets the "current" calling class name.
     *
     * @return the "current" calling class name
     */
    public String getCurrentSourceClass() {
        return this.callStack.peek().getClassName();
    }

    /**
     * Gets the "current" calling method name.
     *
     * @return the "current" calling method name
     */
    public String getCurrentSourceMethod() {
        return this.callStack.peek().getMethodName();
    }

    /**
     * Clear the parameter stack and the call stack.
     *
     */
    @Override
    public void clear() {
        this.callStack.clear();
        super.clear();
    }

    @Override
    public Object push(Object o) {
        pushCallStack();
        return super.push(o);
    }

    /**
     * Pushes a boolean onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(boolean val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes a char onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(char val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes a double onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(double val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes a float onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(float val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes an int onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(int val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes a long onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(long val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pushes a short onto the top of this stack.
     *
     * @param val
     *            the value to push
     */
    public void push(short val) {
        this.push(new BaseTypeWrapper(val));
    }

    /**
     * Pop an object.
     *
     * @return the object
     */
    @Override
    public Object pop() {
        this.callStack.pop();
        return super.pop();
    }

    /**
     * Pop a boolean.
     *
     * @return the value
     */
    public boolean popBoolean() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Boolean value = (Boolean) wrapper.getValue();
        return value.booleanValue();
    }

    /**
     * Pop a char.
     *
     * @return the value
     */
    public char popChar() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Character value = (Character) wrapper.getValue();
        return value.charValue();
    }

    /**
     * Pop a double.
     *
     * @return the value
     */
    public double popDouble() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Double value = (Double) wrapper.getValue();
        return value.doubleValue();
    }

    /**
     * Pop a float.
     *
     * @return the value
     */
    public float popFloat() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Float value = (Float) wrapper.getValue();
        return value.floatValue();
    }

    /**
     * Pop a int.
     *
     * @return the value
     */
    public int popInt() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Integer value = (Integer) wrapper.getValue();
        return value.intValue();
    }

    /**
     * Pop a long.
     *
     * @return the value
     */
    public long popLong() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Long value = (Long) wrapper.getValue();
        return value.longValue();
    }

    /**
     * Pop a short.
     *
     * @return the value
     */
    public short popShort() {
        BaseTypeWrapper wrapper = (BaseTypeWrapper) this.pop();
        Short value = (Short) wrapper.getValue();
        return value.shortValue();
    }

    /*
     * Wrapper of base types.
     */
    class BaseTypeWrapper {

        // the internal value
        private Object value;

        /*
         * Constructs a wrapper object for the base type <code> boolean </code> .
         */
        public BaseTypeWrapper(boolean val) {
            this.value = new Boolean(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> c </code> .
         */
        public BaseTypeWrapper(byte val) {
            this.value = new Byte(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> char </code> .
         */
        public BaseTypeWrapper(char val) {
            this.value = new Character(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> double </code> .
         */
        public BaseTypeWrapper(double val) {
            this.value = new Double(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> float </code> .
         */
        public BaseTypeWrapper(float val) {
            this.value = new Float(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> int </code> .
         */
        public BaseTypeWrapper(int val) {
            this.value = new Integer(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> long </code> .
         */
        public BaseTypeWrapper(long val) {
            this.value = new Long(val);
        }

        /*
         * Constructs a wrapper object for the base type <code> short </code> .
         */
        public BaseTypeWrapper(short val) {
            this.value = new Short(val);
        }

        /*
         * Gets the internal value.
         */
        public Object getValue() {
            return this.value;
        }
    }
}
