/*
 * Copyright (C) 2006-2008 The Android Open Source Project
 *
 * Licensed 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.
 */

#include "SkFloat.h"
#include "SkMath.h"

#define EXP_BIAS    (127+23)

static int get_unsigned_exp(uint32_t packed)
{
    return (packed << 1 >> 24);
}

static unsigned get_unsigned_value(uint32_t packed)
{
    return (packed << 9 >> 9) | (1 << 23);
}

static int get_signed_value(int32_t packed)
{
    return SkApplySign(get_unsigned_value(packed), SkExtractSign(packed));
}

/////////////////////////////////////////////////////////////////////////

int SkFloat::GetShift(int32_t packed, int shift)
{
    if (packed == 0)
        return 0;

    int exp = get_unsigned_exp(packed) - EXP_BIAS - shift;
    int value = get_unsigned_value(packed);

    if (exp >= 0)
    {
        if (exp > 8)    // overflow
            value = SK_MaxS32;
        else
            value <<= exp;
    }
    else
    {
        exp = -exp;
        if (exp > 23)   // underflow
            value = 0;
        else
            value >>= exp;
    }
    return SkApplySign(value, SkExtractSign(packed));
}

/////////////////////////////////////////////////////////////////////////////////////

int32_t SkFloat::SetShift(int value, int shift)
{
    if (value == 0)
        return 0;

    // record the sign and make value positive
    int sign = SkExtractSign(value);
    value = SkApplySign(value, sign);

    if (value >> 24)    // value is too big (has more than 24 bits set)
    {
        int bias = 8 - SkCLZ(value);
        SkASSERT(bias > 0 && bias < 8);
        value >>= bias;
        shift += bias;
    }
    else
    {
        int zeros = SkCLZ(value << 8);
        SkASSERT(zeros >= 0 && zeros <= 23);
        value <<= zeros;
        shift -= zeros;
    }
    // now value is left-aligned to 24 bits
    SkASSERT((value >> 23) == 1);

    shift += EXP_BIAS;
    if (shift < 0)  // underflow
        return 0;
    else
    {
        if (shift > 255)    // overflow
        {
            shift = 255;
            value = 0x00FFFFFF;
        }
        int32_t packed = sign << 31;        // set the sign-bit
        packed |= shift << 23;          // store the packed exponent
        packed |= ((unsigned)(value << 9) >> 9);    // clear 24th bit of value (its implied)

#ifdef SK_DEBUG
        {
            int n;

            n = SkExtractSign(packed);
            SkASSERT(n == sign);
            n = get_unsigned_exp(packed);
            SkASSERT(n == shift);
            n = get_unsigned_value(packed);
            SkASSERT(n == value);
        }
#endif
        return packed;
    }
}

int32_t SkFloat::Neg(int32_t packed)
{
    if (packed)
        packed = packed ^ (1 << 31);
    return packed;
}

int32_t SkFloat::Add(int32_t packed_a, int32_t packed_b)
{
    if (packed_a == 0)
        return packed_b;
    if (packed_b == 0)
        return packed_a;

    int exp_a = get_unsigned_exp(packed_a);
    int exp_b = get_unsigned_exp(packed_b);
    int exp_diff = exp_a - exp_b;

    int shift_a = 0, shift_b = 0;
    int exp;

    if (exp_diff >= 0)
    {
        if (exp_diff > 24)  // B is too small to contribute
            return packed_a;
        shift_b = exp_diff;
        exp = exp_a;
    }
    else
    {
        exp_diff = -exp_diff;
        if (exp_diff > 24)  // A is too small to contribute
            return packed_b;
        shift_a = exp_diff;
        exp = exp_b;
    }

    int value_a = get_signed_value(packed_a) >> shift_a;
    int value_b = get_signed_value(packed_b) >> shift_b;

    return SkFloat::SetShift(value_a + value_b, exp - EXP_BIAS);
}

#include "Sk64.h"

static inline int32_t mul24(int32_t a, int32_t b)
{
    Sk64 tmp;

    tmp.setMul(a, b);
    tmp.roundRight(24);
    return tmp.get32();
}

int32_t SkFloat::Mul(int32_t packed_a, int32_t packed_b)
{
    if (packed_a == 0 || packed_b == 0)
        return 0;

    int exp_a = get_unsigned_exp(packed_a);
    int exp_b = get_unsigned_exp(packed_b);

    int value_a = get_signed_value(packed_a);
    int value_b = get_signed_value(packed_b);

    return SkFloat::SetShift(mul24(value_a, value_b), exp_a + exp_b - 2*EXP_BIAS + 24);
}

int32_t SkFloat::MulInt(int32_t packed, int n)
{
    return Mul(packed, SetShift(n, 0));
}

int32_t SkFloat::Div(int32_t packed_n, int32_t packed_d)
{
    SkASSERT(packed_d != 0);

    if (packed_n == 0)
        return 0;

    int exp_n = get_unsigned_exp(packed_n);
    int exp_d = get_unsigned_exp(packed_d);

    int value_n = get_signed_value(packed_n);
    int value_d = get_signed_value(packed_d);

    return SkFloat::SetShift(SkDivBits(value_n, value_d, 24), exp_n - exp_d - 24);
}

int32_t SkFloat::DivInt(int32_t packed, int n)
{
    return Div(packed, SetShift(n, 0));
}

int32_t SkFloat::Invert(int32_t packed)
{
    return Div(packed, SetShift(1, 0));
}

int32_t SkFloat::Sqrt(int32_t packed)
{
    if (packed < 0)
    {
        SkASSERT(!"can't sqrt a negative number");
        return 0;
    }

    int exp = get_unsigned_exp(packed);
    int value = get_unsigned_value(packed);

    int nexp = exp - EXP_BIAS;
    int root = SkSqrtBits(value << (nexp & 1), 26);
    nexp >>= 1;
    return SkFloat::SetShift(root, nexp - 11);
}

#if defined _WIN32 && _MSC_VER >= 1300  // disable warning : unreachable code
#pragma warning ( push )
#pragma warning ( disable : 4702 )
#endif

int32_t SkFloat::CubeRoot(int32_t packed)
{
    sk_throw();
    return 0;
}

#if defined _WIN32 && _MSC_VER >= 1300
#pragma warning ( pop )
#endif

static inline int32_t clear_high_bit(int32_t n)
{
    return ((uint32_t)(n << 1)) >> 1;
}

static inline int int_sign(int32_t a, int32_t b)
{
    return a > b ? 1 : (a < b ? -1 : 0);
}

int SkFloat::Cmp(int32_t packed_a, int32_t packed_b)
{
    packed_a = SkApplySign(clear_high_bit(packed_a), SkExtractSign(packed_a));
    packed_b = SkApplySign(clear_high_bit(packed_b), SkExtractSign(packed_b));

    return int_sign(packed_a, packed_b);
}

/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////

#ifdef SK_DEBUG

#include "SkRandom.h"
#ifdef SK_CAN_USE_FLOAT
    #include "SkFloatingPoint.h"
#endif

void SkFloat::UnitTest()
{
#ifdef SK_SUPPORT_UNITTEST
    SkFloat a, b, c, d;
    int     n;

    a.setZero();
    n = a.getInt();
    SkASSERT(n == 0);

    b.setInt(5);
    n = b.getInt();
    SkASSERT(n == 5);

    c.setInt(-3);
    n = c.getInt();
    SkASSERT(n == -3);

    d.setAdd(c, b);
    SkDebugf("SkFloat: %d + %d = %d\n", c.getInt(), b.getInt(), d.getInt());    

    SkRandom    rand;

#ifdef SK_CAN_USE_FLOAT
    int i;
    for (i = 0; i < 1000; i++)
    {
        float fa, fb;
        int aa = rand.nextS() >> 14;
        int bb = rand.nextS() >> 14;
        a.setInt(aa);
        b.setInt(bb);
        SkASSERT(a.getInt() == aa);
        SkASSERT(b.getInt() == bb);

        c.setAdd(a, b);
        int cc = c.getInt();
        SkASSERT(cc == aa + bb);

        c.setSub(a, b);
        cc = c.getInt();
        SkASSERT(cc == aa - bb);

        aa >>= 5;
        bb >>= 5;
        a.setInt(aa);
        b.setInt(bb);
        c.setMul(a, b);
        cc = c.getInt();
        SkASSERT(cc == aa * bb);
        /////////////////////////////////////

        aa = rand.nextS() >> 11;
        a.setFixed(aa);
        cc = a.getFixed();
        SkASSERT(aa == cc);

        bb = rand.nextS() >> 11;
        b.setFixed(bb);
        cc = b.getFixed();
        SkASSERT(bb == cc);

        cc = SkFixedMul(aa, bb);
        c.setMul(a, b);
        SkFixed dd = c.getFixed();
        int diff = cc - dd;
        SkASSERT(SkAbs32(diff) <= 1);

        fa = (float)aa / 65536.0f;
        fb = (float)bb / 65536.0f;
        a.assertEquals(fa);
        b.assertEquals(fb);
        fa = a.getFloat();
        fb = b.getFloat();

        c.assertEquals(fa * fb, 1);

        c.setDiv(a, b);
        cc = SkFixedDiv(aa, bb);
        dd = c.getFixed();
        diff = cc - dd;
        SkASSERT(SkAbs32(diff) <= 3);

        c.assertEquals(fa / fb, 1);

        SkASSERT((aa == bb) == (a == b));
        SkASSERT((aa != bb) == (a != b));
        SkASSERT((aa < bb) == (a < b));
        SkASSERT((aa <= bb) == (a <= b));
        SkASSERT((aa > bb) == (a > b));
        SkASSERT((aa >= bb) == (a >= b));

        if (aa < 0)
        {
            aa = -aa;
            fa = -fa;
        }
        a.setFixed(aa);
        c.setSqrt(a);
        cc = SkFixedSqrt(aa);
        dd = c.getFixed();
        SkASSERT(dd == cc);

        c.assertEquals(sk_float_sqrt(fa), 2);

        // cuberoot
#if 0
        a.setInt(1);
        a.cubeRoot();
        a.assertEquals(1.0f, 0);
        a.setInt(8);
        a.cubeRoot();
        a.assertEquals(2.0f, 0);
        a.setInt(27);
        a.cubeRoot();
        a.assertEquals(3.0f, 0);
#endif
    }
#endif
#endif
}

#endif