/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#ifndef hsWideDefined
#define hsWideDefined

#include "hsTypes.h"

struct hsWide {
    Int32   fHi;
    UInt32  fLo;

    hsWide* Set(Int32 lo) { fLo = lo; if (lo < 0) fHi = -1L; else fHi = 0; return this; }
    hsWide* Set(Int32 hi, UInt32 lo) { fHi = hi; fLo = lo; return this; }

    inline hsBool   IsNeg() const { return fHi < 0; }
    inline hsBool   IsPos() const { return fHi > 0 || (fHi == 0 && fLo != 0); }
    inline hsBool   IsZero() const { return fHi == 0 && fLo == 0; }
    inline hsBool   IsWide() const;


    hsBool operator==(const hsWide& b) const { return fHi == b.fHi && fLo == b.fLo; }
    hsBool operator<(const hsWide& b) const { return fHi < b.fHi || fHi == b.fHi && fLo < b.fLo; }
    hsBool operator>( const hsWide& b) const { return fHi > b.fHi || fHi == b.fHi && fLo > b.fLo; }
    hsBool operator!=( const hsWide& b) const { return !( *this == b); }
    hsBool operator<=(const hsWide& b) const { return !(*this > b); }
    hsBool operator>=(const hsWide& b) const { return !(*this < b); }

    inline hsWide*  Negate();
    inline hsWide*  Add(Int32 scaler);
    inline hsWide*  Add(const hsWide* a);
    inline hsWide*  Sub(const hsWide* a);
    inline hsWide*  ShiftLeft(unsigned shift);
    inline hsWide*  ShiftRight(unsigned shift);
    inline hsWide*  RoundRight(unsigned shift);

    inline Int32    AsLong() const;             // return bits 31-0, checking for over/under flow
    inline hsFixed  AsFixed() const;            // return bits 47-16, checking for over/under flow
    inline hsFract  AsFract() const;            // return bits 61-30, checking for over/under flow

    hsWide* Mul(Int32 a);                   // this updates the wide
    hsWide* Mul(Int32 a, Int32 b);          // this sets the wide
    hsWide* Div(Int32 denom);               // this updates the wide
    hsWide* Div(const hsWide* denom);       // this updates the wide

    hsFixed FixDiv(const hsWide* denom) const;
    hsFract FracDiv(const hsWide* denom) const;

    Int32   Sqrt() const;
    Int32   CubeRoot() const;

#if HS_CAN_USE_FLOAT
    double  AsDouble() const { return fHi * double(65536) * double(65536) + fLo; }
    hsWide* Set(double d) 
    { 
        Int32 hi = Int32(d / double(65536) / double(65536));
        Int32 lo = Int32(d - double(hi));
        return Set(hi, lo);
    }
#endif

};

const hsWide kPosInfinity64 = { kPosInfinity32, 0xffffffff };
const hsWide kNegInfinity64 = { kNegInfinity32, 0 };

/////////////////////// Inline implementations ///////////////////////

#define TOP2BITS(n) (UInt32(n) >> 30)
#define TOP3BITS(n) (UInt32(n) >> 29)

#if HS_PIN_MATH_OVERFLOW && HS_DEBUG_MATH_OVERFLOW
    #define hsSignalMathOverflow()  hsDebugMessage("Math overflow", 0)
    #define hsSignalMathUnderflow() hsDebugMessage("Math underflow", 0)
#else
    #define hsSignalMathOverflow()
    #define hsSignalMathUnderflow()
#endif

#define WIDE_ISNEG(hi, lo)                      (Int32(hi) < 0)
#define WIDE_LESSTHAN(hi, lo, hi2, lo2)             ((hi) < (hi2) || (hi) == (hi2) && (lo) < (lo2))
#define WIDE_SHIFTLEFT(outH, outL, inH, inL, shift)     do { (outH) = ((inH) << (shift)) | ((inL) >> (32 - (shift))); (outL) = (inL) << (shift); } while (0)
#define WIDE_NEGATE(hi, lo)                     do { (hi) = ~(hi); if (((lo) = -Int32(lo)) == 0) (hi) += 1; } while (0) 
#define WIDE_ADDPOS(hi, lo, scaler)             do { UInt32 tmp = (lo) + (scaler); if (tmp < (lo)) (hi) += 1; (lo) = tmp; } while (0)
#define WIDE_SUBWIDE(hi, lo, subhi, sublo)          do { (hi) -= (subhi); if ((lo) < (sublo)) (hi) -= 1; (lo) -= (sublo); } while (0) 

/////////////////////// Inline implementations ///////////////////////

inline hsWide* hsWide::Negate()
{
    WIDE_NEGATE(fHi, fLo);
    
    return this;
}

inline hsWide* hsWide::Add(Int32 scaler)
{
    if (scaler >= 0)
        WIDE_ADDPOS(fHi, fLo, scaler);
    else
    {   scaler = -scaler;
        if (fLo < UInt32(scaler))
            fHi--;
        fLo -= scaler;
    }

    return this;
}

inline hsWide* hsWide::Add(const hsWide* a)
{
    UInt32  newLo = fLo + a->fLo;

    fHi += a->fHi;
    if (newLo < (fLo | a->fLo))
        fHi++;
    fLo = newLo;

    return this;
}

inline hsWide* hsWide::Sub(const hsWide* a)
{
    WIDE_SUBWIDE(fHi, fLo, a->fHi, a->fLo);

    return this;
}

inline hsWide* hsWide::ShiftLeft(unsigned shift)
{
    WIDE_SHIFTLEFT(fHi, fLo, fHi, fLo, shift);

    return this;
}

inline hsWide* hsWide::ShiftRight(unsigned shift)
{
    fLo = (fLo >> shift) | (fHi << (32 - shift));
    fHi = fHi >> shift;     // fHi >>= shift;   Treated as logical shift on CW9-WIN32, which breaks for fHi < 0

    return this;
}

inline hsWide* hsWide::RoundRight(unsigned shift)
{
    return this->Add(1L << (shift - 1))->ShiftRight(shift);
}

inline Int32 hsWide::AsLong() const
{
#if HS_PIN_MATH_OVERFLOW
    if (fHi > 0 || fHi == 0 && (Int32)fLo < 0)
    {   hsSignalMathOverflow();
        return kPosInfinity32;
    }
    if (fHi < -1L || fHi == -1L && (Int32)fLo >= 0)
    {   hsSignalMathOverflow();
        return kNegInfinity32;
    }
#endif
    return (Int32)fLo;
}

inline hsBool hsWide::IsWide() const
{
    return (fHi > 0 || fHi == 0 && (Int32)fLo < 0) || (fHi < -1L || fHi == -1L && (Int32)fLo >= 0);
}

inline hsFixed hsWide::AsFixed() const
{
    hsWide tmp = *this;

    return tmp.RoundRight(16)->AsLong();
}

inline hsFract hsWide::AsFract() const
{
    hsWide tmp = *this;

    return tmp.RoundRight(30)->AsLong();
}

#endif