package toy;

/**
 *
 * @author Brian Tsang
 * @version 7.0
 */

public class ToyWord
{
    public static final ToyWord instances[] = new ToyWord[Short.MAX_VALUE - Short.MIN_VALUE];
    public static final ToyWord UNDEFINED = new ToyWord();

    ////////////////////////////////////////////////////////////////////////////
    // Variables
    private short value;
    private boolean isDefined;

    ////////////////////////////////////////////////////////////////////////////
    // The Constructors
    private ToyWord()
    {
        value = 0;
        isDefined = false;
    }

    public ToyWord(short newValue)
    {
        value = newValue;
        isDefined = true;
    }

    ////////////////////////////////////////////////////////////////////////////
    // isDefined() returns wheter or not this word has been defined yet
    public boolean isDefined()
    {
        return isDefined;
    }

    ////////////////////////////////////////////////////////////////////////////
    // getValue() returns the value of this word
    public short getValue()
    {
        return value;
    }



    public static ToyWord parseBinary(boolean newValue[])
    {
        if (newValue != null && newValue.length == 16)
        {
            short shortValue = 0;

            for (int ctr = 15; ctr >= 0; ctr--)
            {
                shortValue = (short)(shortValue << 1);
                if (newValue[ctr])
                    shortValue |= 1;
            }

            return new ToyWord(shortValue);
        }
        else
            return UNDEFINED;
    }

    public static ToyWord parseBinary(String newValue)
    {
        try
        {
            short shortValue = Short.parseShort(newValue, 2);
            return new ToyWord(shortValue);
        }
        catch (Exception e)
        {
            return UNDEFINED;
        }
    }

    public static ToyWord parseDecimal(double newValue)
    {
        return new ToyWord((short)newValue);
    }

    public static ToyWord parseDecimal(String newValue)
    {
        try
        {
            short shortValue = Short.parseShort(newValue);
            return new ToyWord(shortValue);
        }
        catch (Exception e)
        {
            return UNDEFINED;
        }
    }

    public static ToyWord parseHexidecimal(String newValue)
    {
        if (newValue != null && newValue.length() == 4)
            return parseHexidecimal(
                newValue.charAt(0),
                newValue.charAt(1),
                newValue.charAt(2),
                newValue.charAt(3)
                );
        else
            return UNDEFINED;
    }

    public static ToyWord parseHexidecimal(char op, char rd, char rs, char rt)
    {
        if (isHexDigit(op) && isHexDigit(rd) && isHexDigit(rs) && isHexDigit(rt))
        {
            short shortValue;
            shortValue = convertFromHexDigit(op);
            shortValue = (short)(shortValue << 4);

            shortValue |= convertFromHexDigit(rd);
            shortValue = (short)(shortValue << 4);

            shortValue |= convertFromHexDigit(rs);
            shortValue = (short)(shortValue << 4);

            shortValue |= convertFromHexDigit(rt);

            return new ToyWord(shortValue);
        }
        else
            return UNDEFINED;
    }

    ////////////////////////////////////////////////////////////////////////////
    // equals() returns wheter or not two ToyWords are equal
    public boolean equals(Object obj)
    {
        if (obj instanceof ToyWord)
        {
            return ((ToyWord)obj).isDefined() == isDefined &&
                   ((ToyWord)obj).getValue()  == value;
        }
        else
            return false;
    }

    ////////////////////////////////////////////////////////////////////////////
    // toBinaryString() returns a string containing the binary representation
    // of the short.  If the user specifies isFormatted as true, the string will
    // have a space every 4 bits.  Example:
    // "0101 1010 0110 1100"
    public String toBinaryString()
    {
        return toBinaryString(value);
    }

    public String toBinaryString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return toBinaryString(value);
        else
            return "????????????????";
    }

    public String toFormattedBinaryString()
    {
        return toFormattedBinaryString(value);
    }

    public String toFormattedBinaryString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return toFormattedBinaryString(value);
        else
            return "???? ???? ???? ????";
    }

    ////////////////////////////////////////////////////////////////////////////
    // toDecimalString() returns the decimal representation of this word
    public String toDecimalString()
    {
        return String.valueOf(value);
    }

    public String toDecimalString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return String.valueOf(value);
        else
            return ("?????");
    }

    ////////////////////////////////////////////////////////////////////////////
    // toHexString() returns the hexidecimal representation of this word
    public String toHexString()
    {
        return toHexString(value);
    }

    public String toHexString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return toHexString(value);
        else
            return "????";
    }

    ////////////////////////////////////////////////////////////////////////////
    // toString() returns a string containing all the numerical representations
    // of this word
    public String toString()
    {
        return toString(value);
    }

    public String toString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return toString(value);
        else
            return "Uninitialized Memory Location";
    }

    ////////////////////////////////////////////////////////////////////////////
    // toPseudoCodeString() returns a string representing this the pseudocode
    // equivalent of this word (assuming it's a command)
    public String toPseudoCodeString()
    {
        return toPseudoCodeString(value);
    }

    public String toPseudoCodeString(boolean uninitializedErrorEnabled)
    {
        if (isDefined || !uninitializedErrorEnabled)
            return toPseudoCodeString(value);
        else
            return "Uninitialized Memory Location";
    }




    ////////////////////////////////////////////////////////////////////////////
    // toBinaryString() returns a string containing the binary representation
    // of the short.  If the user specifies isFormatted as true, the string will
    // have a space every 4 bits.  Example:
    // "0101 1010 0110 1100"
    public static String toBinaryString(short newValue)
    {
        String answer = "";

        for (int ctr = 0; ctr < 16; ctr++)
        {
            if (((newValue >> ctr) & 1) == 1)
                answer = "1" + answer;
            else
                answer = "0" + answer;
        }

        return answer;
    }

    public static String toFormattedBinaryString(short newValue)
    {
        String answer = "";

        for (int ctr1 = 0; ctr1 < 4; ctr1++)
        {
            for (int ctr2 = 0; ctr2 < 4; ctr2++)
            {
                if (((newValue >> (ctr1 * 4 + ctr2)) & 1) == 1)
                    answer = "1" + answer;
                else
                    answer = "0" + answer;
            }
            if (ctr1 != 3)
                answer = " " + answer;
        }

        return answer;
    }

    ////////////////////////////////////////////////////////////////////////////
    // toHexString() returns the hexidecimal representation of this word
    public static String toHexString(short newValue)
    {
        String answer = "";

        answer += convertToHexDigit((short)((newValue & 0xF000) >> 12));
        answer += convertToHexDigit((short)((newValue & 0x0F00) >> 8));
        answer += convertToHexDigit((short)((newValue & 0x00F0) >> 4));
        answer += convertToHexDigit((short) (newValue & 0x000F));

        return answer;
    }

    ////////////////////////////////////////////////////////////////////////////
    // toString() returns a string containing all the numerical representations
    // of this word
    public static String toString(short newValue)
    {
        return "(" + toFormattedBinaryString(newValue) + ", " + newValue + ")";
    }

    ////////////////////////////////////////////////////////////////////////////
    // toPseudoCodeString() returns a string representing this the pseudocode
    // equivalent of this word (assuming it's a command)
    public static String toPseudoCodeString(short newValue)
    {
        char op, rd, rs, rt;

        op = convertToHexDigit((short)((newValue & 0xF000) >> 12));
        rd = convertToHexDigit((short)((newValue & 0x0F00) >> 8));
        rs = convertToHexDigit((short)((newValue & 0x00F0) >> 4));
        rt = convertToHexDigit((short) (newValue & 0x000F));

        switch (op)
        {
            case '0':
                return "halt";

            case '1':
                if (rs == '0' && rt != '0')
                    return "R[" + rd + "] <- R[" + rt + "]";
                if (rs != '0' && rt == '0')
                    return "R[" + rd + "] <- R[" + rs + "]";
                if (rs == '0' && rt == '0')
                    return "no-op";

                return "R[" + rd + "] <- R[" + rs + "] + R[" + rt + "]";

            case '2':
                if (rs == '0' && rt != '0')
                    return "R[" + rd + "] <- -R[" + rt + "]";
                if (rs != '0' && rt == '0')
                    return "R[" + rd + "] <- R[" + rs + "]";
                if (rs == '0' && rt == '0')
                    return "R[" + rd + "] <- 0000";

                return "R[" + rd + "] <- R[" + rs + "] - R[" + rt + "]";

            case '3':
                if (rs == '0' || rt == '0')
                    return "R[" + rd + "] <- 0000";
                if (rs == rt)
                    return "R[" + rd + "] <- R[" + rs + "]";

                return "R[" + rd + "] <- R[" + rs + "] & R[" + rt + "]";

            case '4':
                if (rs == '0' && rt != '0')
                    return "R[" + rd + "] <- R[" + rt + "]";
                if (rs != '0' && rt == '0')
                    return "R[" + rd + "] <- R[" + rs + "]";
                if (rs == '0' && rt == '0')
                    return "R[" + rd + "] <- 0000";

                return "R[" + rd + "] <- R[" + rs + "] ^ R[" + rt + "]";

            case '5':
                if (rs == '0')
                    return "R[" + rd + "] <- 0000";
                if (rt == '0')
                    return "R[" + rd + "] <- R[" + rs + "]";

                return "R[" + rd + "] <- R[" + rs + "] << R[" + rt + "]";

            case '6':
                if (rs == '0')
                    return "R[" + rd + "] <- 0000";
                if (rt == '0')
                    return "R[" + rd + "] <- R[" + rs + "]";

                return "R[" + rd + "] <- R[" + rs + "] >> R[" + rt + "]";

            case '7':
                return "R[" + rd + "] <- 00" + rs + rt;

            case '8':
                if (rs == 'F' && rt == 'F')
                    return "read R[" + rd + "]";
                else
                    return "R[" + rd + "] <- mem[" + rs + rt + "]";

            case '9':
                if (rs == 'F' && rt == 'F')
                    return "write R[" + rd + "]";
                else
                    return "mem[" + rs + rt + "] <- R[" + rd + "]";

            case 'A':
                return "R[" + rd + "] <- mem[R[" + rt + "]]";

            case 'B':
                return "mem[R[" + rt + "]] <- R[" + rd + "]";

            case 'C':
                if (rd == '0')
                    return "goto " + rs + rt;

                return "if (R[" + rd + "] == 0) goto " + rs + rt;

            case 'D':
                return "if (R[" + rd + "] > 0) goto " + rs + rt;

            case 'E':
                return "goto R[" + rd + "]";

            case 'F':
                return "R[" + rd + "] <- pc; goto " + rs + rt;
        }

        return "";
    }


    ////////////////////////////////////////////////////////////////////////////
    // add() is a static function that adds two ToyWords together and returns
    // a new ToyWord containing the sum (it can throw an overflow or
    // uninitialized ToyException if the user permits them)
    public static ToyWord add(ToyWord a, ToyWord b,
                              boolean uninitializedErrorEnabled,
                              boolean overflowErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        //we add the values as ints so we can check for overflow
        long sum = a.getValue() + b.getValue();

        if ((sum < Short.MIN_VALUE || sum > Short.MAX_VALUE)
            && overflowErrorEnabled)
            throw new ToyException(ToyException.OVERFLOW);

        return new ToyWord((short)sum);
    }

    ////////////////////////////////////////////////////////////////////////////
    // subtract() is a static function that subtracts two ToyWords from each
    // other and returns a new ToyWord containing the difference (it can throw
    // an overflow or uninitialized ToyException if the user permits them)
    public static ToyWord subtract(ToyWord a, ToyWord b,
                              boolean uninitializedErrorEnabled,
                              boolean overflowErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        //we subtract the values as ints so we can check for overflow
        long difference = a.getValue() - b.getValue();

        if ((difference < Short.MIN_VALUE || difference > Short.MAX_VALUE)
            && overflowErrorEnabled)
            throw new ToyException(ToyException.OVERFLOW);

        return new ToyWord((short)difference);
    }

    ////////////////////////////////////////////////////////////////////////////
    // and() is a static function that and's two ToyWords and returns a new
    // ToyWord containing the result (it can throw an uninitialized
    // ToyException if the user permits it)
    public static ToyWord and(ToyWord a, ToyWord b,
                              boolean uninitializedErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        return new ToyWord((short)(a.getValue() & b.getValue()));
    }

    ////////////////////////////////////////////////////////////////////////////
    // xor() is a static function that xor's two ToyWords and returns a new
    // ToyWord containing the result (it can throw an uninitialized
    // ToyException if the user permits it)
    public static ToyWord xor(ToyWord a, ToyWord b,
                              boolean uninitializedErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        return new ToyWord((short)(a.getValue() ^ b.getValue()));
    }

    ////////////////////////////////////////////////////////////////////////////
    // leftShift() is a static function that takes one ToyWord and a short and
    // returns a ToyWord containg the left-shifted value (it can throw an
    // uninitialized ToyException if the user permits it)
    public static ToyWord leftShift(ToyWord a, ToyWord b,
                                    boolean uninitializedErrorEnabled,
                                    boolean overflowErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        if ((b.getValue() & 0xF) != b.getValue())
            throw new ToyException(ToyException.SHIFT_OUT_OF_BOUNDS);

        return new ToyWord((short)(a.getValue() << (b.getValue() & 0xF)));
    }

    ////////////////////////////////////////////////////////////////////////////
    // rightShift() is a static function that takes one ToyWord and a short and
    // returns a ToyWord containg the right-shifted value (it can throw an
    // uninitialized ToyException if the user permits it)
    public static ToyWord rightShift(ToyWord a, ToyWord b,
                                     boolean uninitializedErrorEnabled,
                                     boolean overflowErrorEnabled)
                                                            throws ToyException
    {
        if ((!a.isDefined() || !b.isDefined()) && uninitializedErrorEnabled)
            throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

        if ((b.getValue() & 0xF) != b.getValue())
            throw new ToyException(ToyException.SHIFT_OUT_OF_BOUNDS);

        return new ToyWord((short)(a.getValue() >>> (b.getValue() & 0xF)));
    }


    ////////////////////////////////////////////////////////////////////////////
    // isHexDigit() is a test to see whether or not a character is a hexidecimal
    // digit
    public static boolean isHexDigit(char c)
    {
        return c >= '0' && c <= '9' ||
               c >= 'A' && c <= 'F' ||
               c >= 'a' && c <= 'f';
    }

    ////////////////////////////////////////////////////////////////////////////
    // convertFromHexDigit() returns a short containing the value of a char
    // in base 16
    public static short convertFromHexDigit(char c)
    {
        if (c >= '0' && c <= '9')
            return (short)(c - '0');

        if (c >= 'A' && c <= 'F')
            return (short)(c - 'A' + 10);

        if (c >= 'a' && c <= 'f')
            return (short)(c - 'a' + 10);

        return -1;
    }

    ////////////////////////////////////////////////////////////////////////////
    // convertFromHexDigit() returns a char corresponding to the base 16
    // representation of a short
    public static char convertToHexDigit(short value)
    {
        if (value >= 0 && value <= 9)
            return (char)(value + '0');

        if (value >= 10 && value <= 16)
            return (char)(value + 'A' - 10);

        return '?';
    }
}
