package php.runtime.memory;

import php.runtime.Memory;
import php.runtime.memory.support.MemoryStringUtils;

import java.nio.charset.Charset;

public class LongMemory extends Memory {

    protected final static int MAX_CACHE_STRING = 10000;
    protected final static int MAX_NEG_CACHE = Short.MAX_VALUE / 7;
    protected final static int MAX_POS_CACHE = Short.MAX_VALUE - MAX_NEG_CACHE;

    protected final static String[] STRING_VALUES;
    protected final static LongMemory[] CACHE;

    static {
        STRING_VALUES = new String[MAX_CACHE_STRING];
        for(int i = 0; i < MAX_CACHE_STRING; i++){
            STRING_VALUES[i] = String.valueOf(i);
        }

        CACHE = new LongMemory[MAX_POS_CACHE + MAX_NEG_CACHE];
        for(int i = -MAX_NEG_CACHE; i < MAX_POS_CACHE; i++){
            CACHE[i + MAX_NEG_CACHE] = new LongMemory(i);
        }
    }

    public long value;

    public LongMemory(long value) {
        super(Type.INT);
        this.value = value;
    }

    public static Memory valueOf(long value){
        if (value >= -MAX_NEG_CACHE && value < MAX_POS_CACHE)
            return CACHE[(int)value + MAX_NEG_CACHE];
        else
            return new LongMemory(value);
    }

    public static Memory valueOf(int value){
        if (value >= -MAX_NEG_CACHE && value <= MAX_NEG_CACHE)
            return CACHE[value + MAX_NEG_CACHE];
        else
            return new LongMemory(value);
    }

    public static Memory valueOf(byte value){
        return valueOf((int)value);
    }

    public static Memory valueOf(short value){
        return valueOf((int)value);
    }

    public static Memory valueOf(boolean value){
        return value ? TRUE : FALSE;
    }

    @Override
    public long toLong() {
        return value;
    }

    @Override
    public double toDouble() {
        return value;
    }

    @Override
    public boolean toBoolean() {
        return value != 0;
    }

    @Override
    public Memory toNumeric() {
        return this;
    }

    @Override
    public String toString() {
        if (value >= 0 && value < MAX_CACHE_STRING)
            return STRING_VALUES[(int)value];

        return String.valueOf(value);
    }

    @Override
    public Memory inc() {
        return new LongMemory(value + 1);
    }

    @Override
    public Memory dec() {
        return new LongMemory(value - 1);
    }

    @Override
    public Memory negative() {
        return new LongMemory(- value);
    }

    @Override
    public Memory plus(Memory memory) {
        switch (memory.type){
            case INT: return LongMemory.valueOf(value + ((LongMemory) memory).value);
            case DOUBLE: return new DoubleMemory(value + ((DoubleMemory)memory).value);
            case REFERENCE: return plus(memory.toValue());
            default: return plus(memory.toNumeric());
        }
    }

    @Override
    public Memory minus(Memory memory) {
        switch (memory.type){
            case INT: return LongMemory.valueOf(value - ((LongMemory) memory).value);
            case DOUBLE: return new DoubleMemory(value - ((DoubleMemory)memory).value);
            case REFERENCE: return minus(memory.toValue());
            default: return minus(memory.toNumeric());
        }
    }

    @Override
    public Memory mul(Memory memory) {
        switch (memory.type){
            case INT: return LongMemory.valueOf(value * ((LongMemory) memory).value);
            case DOUBLE: return new DoubleMemory(value * ((DoubleMemory)memory).value);
            case REFERENCE: return mul(memory.toValue());
            default: return mul(memory.toNumeric());
        }
    }

    @Override
    public Memory pow(Memory memory) {
        switch (memory.type){
            case INT: return pow(((LongMemory) memory).value);
            case DOUBLE: return pow(((DoubleMemory) memory).value);
            case REFERENCE: return pow(memory.toImmutable());
            default: return pow(memory.toNumeric());
        }
    }

    @Override
    public Memory div(Memory memory) {
        switch (memory.type){
            case INT: {
                long tmp = ((LongMemory)memory).value;
                if (tmp == 0) return FALSE;

                if (value % tmp == 0)
                    return LongMemory.valueOf(value / tmp);
                else
                    return new DoubleMemory(value / (double)tmp);
            }
            case DOUBLE:
                if (((DoubleMemory)memory).value == 0) return FALSE;
                return new DoubleMemory(value / ((DoubleMemory)memory).value);
            case REFERENCE: return div(memory.toValue());
            default: return div(memory.toNumeric());
        }
    }

    @Override
    public Memory div(long value) {
        if (value == 0)
            return FALSE;

        if (this.value % value == 0)
            return LongMemory.valueOf(this.value / value);
        else
            return new DoubleMemory(this.value / (double)value);
    }

    @Override
    public boolean identical(Memory memory) {
        return memory.getRealType() == Type.INT && ((LongMemory)memory).value == value;
    }

    @Override
    public boolean equal(Memory memory) {
        switch (memory.type){
            case INT: return ((LongMemory)memory).value == value;
            case DOUBLE: return ((DoubleMemory)memory).value == value;
            case STRING: return equal(memory.toNumeric());
            case REFERENCE: return equal(memory.toValue());
            default:
                return value == toLong();
        }
    }

    @Override
    public boolean equal(long value) {
        return this.value == value;
    }

    @Override
    public boolean equal(double value) {
        return this.value == value;
    }

    @Override
    public boolean notEqual(Memory memory) {
        switch (memory.type){
            case INT: return ((LongMemory)memory).value != value;
            case DOUBLE: return ((DoubleMemory)memory).value != value;
            case STRING: return notEqual(memory.toNumeric());
            case REFERENCE: return smaller(memory.toValue());
            default:
                return value != toLong();
        }
    }

    @Override
    public String concat(Memory memory) {
        return toString().concat(memory.toString());
    }

    @Override
    public boolean smaller(Memory memory) {
        switch (memory.type){
            case DOUBLE: return value < ((DoubleMemory)memory).value;
            case INT: return value < ((LongMemory)memory).value;
            case STRING: return smaller(memory.toNumeric());
            case REFERENCE: return smaller(memory.toValue());
            default:
                return value < toLong();
        }
    }

    @Override
    public boolean smallerEq(Memory memory) {
        switch (memory.type){
            case DOUBLE: return value <= ((DoubleMemory)memory).value;
            case INT: return value <= ((LongMemory)memory).value;
            case STRING: return smallerEq(memory.toNumeric());
            case REFERENCE: return smallerEq(memory.toValue());
            default:
                return value <= toLong();
        }
    }

    @Override
    public boolean greater(Memory memory) {
        return memory.smaller(this);
    }

    @Override
    public boolean greaterEq(Memory memory) {
        return memory.smallerEq(this);
    }

    @Override
    public Memory minus(long value) {
        return LongMemory.valueOf(this.value - value);
    }

    @Override
    public Memory plus(long value) {
        return LongMemory.valueOf(this.value + value);
    }

    @Override
    public Memory div(boolean value) {
        if (!value)
            return FALSE;
        return this;
    }

    @Override
    public int hashCode() {
        return (int) (value ^ (value >>> 32));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof LongMemory)) return false;

        LongMemory that = (LongMemory) o;
        return value == that.value;
    }

    @Override
    public byte[] getBinaryBytes(Charset charset) {
        return MemoryStringUtils.getBinaryBytes(this);
    }
}

@@ -5504,16 +5504,23 @@
 alue == 
+memory.
 toLong()

Fix #246. Bug in LongMemory.equal().