/**
 * Copyright 2005-2007 Xue Yong Zhi, Ye Zheng, Jie Li
 * Distributed under the BSD License
 */

package com.xruby.runtime.builtin;

import java.math.BigInteger;

import com.xruby.runtime.lang.*;
import com.xruby.runtime.lang.annotation.RubyLevelClass;
import com.xruby.runtime.lang.annotation.RubyLevelMethod;

@RubyLevelClass(name="Fixnum", superclass="Integer")
public class RubyFixnum extends RubyInteger {
    private final long value_;

    RubyFixnum(int i) {
        value_ = i;
    }
    RubyFixnum(long i) {
        value_ = i;
    }
    
    public void setRubyClass(RubyClass klass) {
		throw new RubyException(RubyRuntime.TypeErrorClass, this.getRubyClass().getName() + " can't be set class");
	}
	
	public RubyClass getSingletonClass() {
    	throw new RubyException(RubyRuntime.TypeErrorClass, this.getRubyClass().getName() + " can't define singleton");
    }

	public RubyValue clone() {
		RubyFixnum cl = new RubyFixnum(this.value_);
		cl.doClone((RubyValue)this);
		return cl;
	}
    
	public int toInt() {
		return (int)value_;
	}
	public long toLong() {
		return value_;
	}
	
	public double toFloat() {
		return this.value_;
	}
	
	public RubyFloat toRubyFloat() {
		return ObjectFactory.createFloat(this.value_);
	}

	public RubyFixnum convertToInteger() {
		return this;
    }

	public int hashCode() {
        return (int)value_;
    }

    public RubyClass getRubyClass() {
        return RubyRuntime.FixnumClass;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o instanceof RubyFixnum) {
            return value_ == ((RubyFixnum)o).value_;
        } else {
            return super.equals(o);
        }
    }

    public String toString() {
        return Long.toString(value_);
    }

    public String toString(int radix) {
        return Long.toString(value_, radix);
    }
    
    @RubyLevelMethod(name="-@")
    public RubyValue uminus() {
    	return ObjectFactory.createFixnum(-this.value_);
    }
    
    @RubyLevelMethod(name="+")
    public RubyValue opPlus(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		return RubyBignum.bignorm((long)this.value_ + (long)((RubyFixnum)v).value_);
    	}
    	
    	if (v instanceof RubyFloat) {
            return ObjectFactory.createFloat(value_ + v.toFloat());
    	}
    	
    	if (v instanceof RubyBignum) {
    		BigInteger bigValue1 = BigInteger.valueOf(value_);
    		BigInteger bigValue2 = ((RubyBignum)v).getInternal();
    		return RubyBignum.bignorm(bigValue1.add(bigValue2));
    	}
    	
    	return coerceBin(RubyID.plusID, v);
    }
    
    @RubyLevelMethod(name="-")
    public RubyValue opMinus(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		return RubyBignum.bignorm((long)this.value_ - (long)((RubyFixnum)v).value_);
    	}
    	
    	if (v instanceof RubyFloat) {
    		return ObjectFactory.createFloat(this.value_ - v.toFloat());
    	}
    	
    	if (v instanceof RubyBignum) {
    		BigInteger bigValue1 = BigInteger.valueOf(this.value_);
            BigInteger bigValue2 = ((RubyBignum)v).getInternal();
            return RubyBignum.bignorm(bigValue1.subtract(bigValue2));
    	}
    	
    	return coerceBin(RubyID.subID, v);
    }
    
    @RubyLevelMethod(name="*")
    public RubyValue opMul(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		return RubyBignum.bignorm((long)this.value_ * (long)((RubyFixnum)v).value_);
    	}
    	
    	if (v instanceof RubyFloat) {
    		return ObjectFactory.createFloat(this.value_ * v.toFloat());
    	}
    	
    	if (v instanceof RubyBignum) {
    		BigInteger bigValue1 = BigInteger.valueOf(this.value_);
            BigInteger bigValue2 = ((RubyBignum)v).getInternal();
            return RubyBignum.bignorm(bigValue1.multiply(bigValue2));
    	}
    	
    	return coerceBin(RubyID.mulID, v);
    }
    
    @RubyLevelMethod(name="/")
    public RubyValue opDiv(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		long intValue1 = this.value_;
            long intValue2 = ((RubyFixnum)v).value_;
            if (intValue2 == 0) {
            	zeroDiv();
            }
    		long div = intValue1 / intValue2;
            long mod = intValue1 - div * intValue2;
            if (mod != 0 && div < 0) {
                --div;
            }
            return RubyBignum.bignorm(div);
    	} else if (v instanceof RubyFloat) {
    		return ObjectFactory.createFloat(this.value_ / v.toFloat());
    	} else if (v instanceof RubyBignum) {
    		BigInteger bigValue1 = BigInteger.valueOf(this.value_);
            BigInteger bigValue2 = ((RubyBignum)v).getInternal();
            return RubyBignum.bignorm(bigValue1.divide(bigValue2));
    	} else {
    		return coerceBin(RubyID.divID, v);
    	}
    }
    
    @RubyLevelMethod(name="%")
    public RubyValue mod(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		return RubyBignum.bignorm(this.value_ % ((RubyFixnum)v).value_);
    	}
    	
    	return coerceBin(RubyID.modID, v);
    }
    
    @RubyLevelMethod(name="**")
    public RubyValue pow(RubyValue v) {
    	if (v instanceof RubyFixnum) {
    		long p = ((RubyFixnum)v).value_;
    		if (p == 0) {
    			return ObjectFactory.FIXNUM1;
    		} else if (p == 1) {
    			return this;
    		}
    		
    		if (p > 0) {
    			BigInteger b = BigInteger.valueOf(this.value_);
    			return RubyBignum.bignorm(b.pow((int)p));
    		}
    		
    		return ObjectFactory.createFloat(Math.pow(this.value_, p));
    	} else if (v instanceof RubyFloat) {
    		return ObjectFactory.createFloat(Math.pow(this.value_, v.toFloat()));
    	}
    	
    	return coerceBin(RubyID.powID, v);
    }
    
    
    @RubyLevelMethod(name="~")
    public RubyValue opRev() {
    	return RubyBignum.bignorm(~this.value_);
    }
    
    @RubyLevelMethod(name="to_f")
    public RubyFloat convertToFloat() {
    	return ObjectFactory.createFloat(this.value_);
    }
    
    @RubyLevelMethod(name="<<")
    public RubyValue lshift(RubyValue arg) {
    	return lshift(arg.toInt());
    }
    
    @RubyLevelMethod(name=">>")
    public RubyValue rshift(RubyValue arg) {
    	return rshift(arg.toInt());
    }
    
    private static int BIT_SIZE = 32;
    
    private RubyValue lshift(int i) {
    	if (i == 0) {
    		return this;
    	} else if (i < 0) {
    		return rshift(-i);
    	}
    	// FIXME: TO BE IMPROVED
    	BigInteger bigValue1 = BigInteger.valueOf(this.value_);
        return RubyBignum.bignorm(bigValue1.shiftLeft(i));
    }

	private RubyValue rshift(int i) {
		if (i == 0) {
    		return this;
    	} else if (i < 0) {
    		return lshift(-i);
    	}
		
		if (i >= BIT_SIZE - 1) {
    		if (this.value_ < 0) {
    			return ObjectFactory.FIXNUM_NEGATIVE_ONE;
    		}
    		
    		return ObjectFactory.FIXNUM0;
    	}
    	
    	return ObjectFactory.createFixnum(this.value_ >> i);
	}
	
	@RubyLevelMethod(name="==")
	public RubyValue opEqual(RubyValue arg) {
		if (arg == this) {
			return RubyConstant.QTRUE;
		}
		
		if (arg instanceof RubyFixnum) {
			return ObjectFactory.createBoolean(this.value_ == ((RubyFixnum)arg).value_);
		}
		
		return RubyAPI.callOneArgMethod(arg, this, null, RubyID.equalID);
	}
	
	@RubyLevelMethod(name="<=")
	public RubyValue opLe(RubyValue v) {
		if (v instanceof RubyFixnum) {
			return ObjectFactory.createBoolean(this.value_ <= ((RubyFixnum)v).value_);
		} else if (v instanceof RubyFloat) {
            return ObjectFactory.createBoolean(this.value_ <= v.toFloat());
		}
		
		return coerceRelop(RubyID.leID, v);
	}
	
	@RubyLevelMethod(name="<")
	public RubyValue opLt(RubyValue v) {
		if (v instanceof RubyFixnum) {
			return ObjectFactory.createBoolean(this.value_ < ((RubyFixnum)v).value_);
		} else if (v instanceof RubyFloat) {
            return ObjectFactory.createBoolean(this.value_ < v.toFloat());
		}
		
		return coerceRelop(RubyID.ltID, v);
	}
	
	@RubyLevelMethod(name=">=")
	public RubyValue opGe(RubyValue v) {
		if (v instanceof RubyFixnum) {
			return ObjectFactory.createBoolean(this.value_ >= ((RubyFixnum)v).value_);
		} else if (v instanceof RubyFloat) {
            return ObjectFactory.createBoolean(this.value_ >= v.toFloat());
		}
		
		return coerceRelop(RubyID.geID, v);
	}
	
	@RubyLevelMethod(name=">")
	public RubyValue opGt(RubyValue v) {
		if (v instanceof RubyFixnum) {
			return ObjectFactory.createBoolean(this.value_ > ((RubyFixnum)v).value_);
		} else if (v instanceof RubyFloat) {
            return ObjectFactory.createBoolean(this.value_ > v.toFloat());
		}
		
		return coerceRelop(RubyID.gtID, v);
	}
	
	@RubyLevelMethod(name="<=>")
	public RubyValue opCmp(RubyValue v) {
		if (this == v) {
			return ObjectFactory.FIXNUM0;
		}
		
		if (v instanceof RubyFixnum) {
			long a = ((RubyFixnum)v).value_;
			if (this.value_ > a) {
				return ObjectFactory.FIXNUM1;
			} else if (this.value_ == a) {
				return ObjectFactory.FIXNUM0;
			} else {
				return ObjectFactory.FIXNUM_NEGATIVE_ONE;
			}
		}
		
		return coerceCmp(RubyID.unequalID, v);
	}
	
	@RubyLevelMethod(name="|")
	public RubyValue opOr(RubyValue v) {
		if (v instanceof RubyBignum) {
			return ((RubyBignum)v).op_bor(this);
		}
		
		return RubyBignum.bignorm(this.value_ | v.toLong());
	}
	
	@RubyLevelMethod(name="&")
	public RubyValue opAnd(RubyValue v) {
		if (v instanceof RubyBignum) {
			return ((RubyBignum)v).op_band(this);
		}
		
		return RubyBignum.bignorm(this.value_ & v.toLong());
	}
	
	@RubyLevelMethod(name="^")
	public RubyValue opXor(RubyValue v) {
		if (v instanceof RubyBignum) {
			return ((RubyBignum)v).op_bxor(this);
		}
		
		return RubyBignum.bignorm(this.value_ ^ v.toLong());
	}
	
	@RubyLevelMethod(name="to_s")
	public RubyString to_s() {
		return ObjectFactory.createString(this.toString());
	}
	
	@RubyLevelMethod(name="to_s")
	public RubyString to_s(RubyValue v) {
		 int radix = v.toInt();
         if (radix < 2 || radix > 36) {
             throw new RubyException(RubyRuntime.ArgumentErrorClass, "illegal radix " + radix);
         }
         
         return ObjectFactory.createString(this.toString(radix));
	}
	
	@RubyLevelMethod(name="quo")
	public RubyFloat quo(RubyValue v) {
        if (v instanceof RubyFixnum) {
        	return ObjectFactory.createFloat(this.value_ / ((RubyFixnum)v).value_);
        }
        
        // FIXME: should be coerced.
        throw new RubyException(RubyRuntime.TypeErrorClass, v.getRubyClass().getName() + " can't be coersed into Fixnum");
	}
	
	@RubyLevelMethod(name="[]")
	public RubyFixnum aref(RubyValue idx) {
		if (idx instanceof RubyBignum) {
			idx = RubyBignum.bignorm(idx);
			if (!(idx instanceof RubyFixnum)) {
				if (this.value_ > 0 || ((RubyBignum)idx).getInternal().compareTo(BigInteger.ZERO) > 0) {
					return ObjectFactory.FIXNUM0;
				} else {
					return ObjectFactory.FIXNUM1;
				}
			}
		}
		
		int i = idx.toInt();
		if (i < 0 || i > BIT_SIZE) {
			return ObjectFactory.FIXNUM0;
		}
		
		if ((this.value_ & (1l << i)) > 0) {
			return ObjectFactory.FIXNUM1;
		}
		
		return ObjectFactory.FIXNUM0;
	}

	protected RubyValue doStep(RubyValue toArg, RubyValue stepArg, RubyBlock block) {
		if ((toArg instanceof RubyFixnum) && (stepArg instanceof RubyFixnum)) {
			long i = this.value_;
			long end = ((RubyFixnum)toArg).value_;
			long diff = ((RubyFixnum)stepArg).value_;
			if (diff > 0) {
				while (i <= end) {
					RubyValue v = block.invoke(this, ObjectFactory.createFixnum(i));
                    if (block.breakedOrReturned()) {
                        return v;
                    }
                    i += diff;
				}
			} else {
				while (i >= end) {
					RubyValue v = block.invoke(this, ObjectFactory.createFixnum(i));
                    if (block.breakedOrReturned()) {
                        return v;
                    }
                    i += diff;
				}
			}
			
			return this;
		}
		
		return super.doStep(toArg, stepArg, block);
	}

	public RubyValue times(RubyBlock block) {
		long value = this.value_;
        for (int i = 0; i < value; ++i) {
            RubyValue v = block.invoke(this, ObjectFactory.createFixnum(i));
            if (block.breakedOrReturned()) {
                return v;
            }
        }
        
        return this;
	}
}