/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "Test.h"
#include "SkRandom.h"
#include <math.h>

struct BoolTable {
    int8_t  zero, pos, neg, toBool, sign;
};

static void bool_table_test(skiatest::Reporter* reporter,
                            const Sk64& a, const BoolTable& table)
{
    REPORTER_ASSERT(reporter, a.isZero() != a.nonZero());

    REPORTER_ASSERT(reporter, !a.isZero() == !table.zero);
    REPORTER_ASSERT(reporter, !a.isPos() == !table.pos);
    REPORTER_ASSERT(reporter, !a.isNeg() == !table.neg);
    REPORTER_ASSERT(reporter, a.getSign() == table.sign);
}

#ifdef SkLONGLONG
    static SkLONGLONG asLL(const Sk64& a)
    {
        return ((SkLONGLONG)a.fHi << 32) | a.fLo;
    }
#endif

static void TestSk64(skiatest::Reporter* reporter) {
    enum BoolTests {
        kZero_BoolTest,
        kPos_BoolTest,
        kNeg_BoolTest
    };
    static const BoolTable gBoolTable[] = {
        { 1, 0, 0, 0, 0 },
        { 0, 1, 0, 1, 1 },
        { 0, 0, 1, 1, -1 }
    };

    Sk64    a, b, c;

    a.fHi = a.fLo = 0;
    b.set(0);
    c.setZero();
    REPORTER_ASSERT(reporter, a == b);
    REPORTER_ASSERT(reporter, a == c);
    bool_table_test(reporter, a, gBoolTable[kZero_BoolTest]);

    a.fHi = 0;  a.fLo = 5;
    b.set(5);
    REPORTER_ASSERT(reporter, a == b);
    REPORTER_ASSERT(reporter, a.is32() && a.get32() == 5 && !a.is64());
    bool_table_test(reporter, a, gBoolTable[kPos_BoolTest]);

    a.fHi = -1; a.fLo = (uint32_t)-5;
    b.set(-5);
    REPORTER_ASSERT(reporter, a == b);
    REPORTER_ASSERT(reporter, a.is32() && a.get32() == -5 && !a.is64());
    bool_table_test(reporter, a, gBoolTable[kNeg_BoolTest]);

    a.setZero();
    b.set(6);
    c.set(-6);
    REPORTER_ASSERT(reporter, a != b && b != c && a != c);
    REPORTER_ASSERT(reporter, !(a == b) && !(a == b) && !(a == b));
    REPORTER_ASSERT(reporter, a < b && b > a && a <= b && b >= a);
    REPORTER_ASSERT(reporter, c < a && a > c && c <= a && a >= c);
    REPORTER_ASSERT(reporter, c < b && b > c && c <= b && b >= c);

    // Now test add/sub

    SkRandom    rand;
    int         i;

    for (i = 0; i < 1000; i++)
    {
        int aa = rand.nextS() >> 1;
        int bb = rand.nextS() >> 1;
        a.set(aa);
        b.set(bb);
        REPORTER_ASSERT(reporter, a.get32() == aa && b.get32() == bb);
        c = a; c.add(bb);
        REPORTER_ASSERT(reporter, c.get32() == aa + bb);
        c = a; c.add(-bb);
        REPORTER_ASSERT(reporter, c.get32() == aa - bb);
        c = a; c.add(b);
        REPORTER_ASSERT(reporter, c.get32() == aa + bb);
        c = a; c.sub(b);
        REPORTER_ASSERT(reporter, c.get32() == aa - bb);
    }

#ifdef SkLONGLONG
    for (i = 0; i < 1000; i++)
    {
        rand.next64(&a); //a.fHi >>= 1; // avoid overflow
        rand.next64(&b); //b.fHi >>= 1; // avoid overflow

        if (!(i & 3))   // want to explicitly test these cases
        {
            a.fLo = 0;
            b.fLo = 0;
        }
        else if (!(i & 7))  // want to explicitly test these cases
        {
            a.fHi = 0;
            b.fHi = 0;
        }

        SkLONGLONG aa = asLL(a);
        SkLONGLONG bb = asLL(b);

        REPORTER_ASSERT(reporter, (a < b) == (aa < bb));
        REPORTER_ASSERT(reporter, (a <= b) == (aa <= bb));
        REPORTER_ASSERT(reporter, (a > b) == (aa > bb));
        REPORTER_ASSERT(reporter, (a >= b) == (aa >= bb));
        REPORTER_ASSERT(reporter, (a == b) == (aa == bb));
        REPORTER_ASSERT(reporter, (a != b) == (aa != bb));

        c = a; c.add(b);
        REPORTER_ASSERT(reporter, asLL(c) == aa + bb);
        c = a; c.sub(b);
        REPORTER_ASSERT(reporter, asLL(c) == aa - bb);
        c = a; c.rsub(b);
        REPORTER_ASSERT(reporter, asLL(c) == bb - aa);
        c = a; c.negate();
        REPORTER_ASSERT(reporter, asLL(c) == -aa);

        int bits = rand.nextU() & 63;
        c = a; c.shiftLeft(bits);
        REPORTER_ASSERT(reporter, asLL(c) == (aa << bits));
        c = a; c.shiftRight(bits);
        REPORTER_ASSERT(reporter, asLL(c) == (aa >> bits));
        c = a; c.roundRight(bits);

        SkLONGLONG tmp;

        tmp = aa;
        if (bits > 0)
            tmp += (SkLONGLONG)1 << (bits - 1);
        REPORTER_ASSERT(reporter, asLL(c) == (tmp >> bits));

        c.setMul(a.fHi, b.fHi);
        tmp = (SkLONGLONG)a.fHi * b.fHi;
        REPORTER_ASSERT(reporter, asLL(c) == tmp);
    }


    for (i = 0; i < 100000; i++)
    {
        Sk64    wide;
        int32_t denom = rand.nextS();

        while (denom == 0)
            denom = rand.nextS();
        wide.setMul(rand.nextS(), rand.nextS());
        SkLONGLONG check = wide.getLongLong();

        wide.div(denom, Sk64::kTrunc_DivOption);
        check /= denom;
        SkLONGLONG w = wide.getLongLong();

        REPORTER_ASSERT(reporter, check == w);

#ifdef SK_CAN_USE_FLOAT
        wide.setMul(rand.nextS(), rand.nextS());
        wide.abs();
        denom = wide.getSqrt();
        int32_t ck = (int32_t)sqrt((double)wide.getLongLong());
        int diff = denom - ck;
        REPORTER_ASSERT(reporter, SkAbs32(diff) <= 1);

        wide.setMul(rand.nextS(), rand.nextS());
        Sk64    dwide;
        dwide.setMul(rand.nextS(), rand.nextS());
        SkFixed fixdiv = wide.getFixedDiv(dwide);
        double dnumer = (double)wide.getLongLong();
        double ddenom = (double)dwide.getLongLong();
        double ddiv = dnumer / ddenom;
        SkFixed dfixdiv;
        if (ddiv >= (double)SK_MaxS32 / (double)SK_Fixed1)
            dfixdiv = SK_MaxS32;
        else if (ddiv <= -(double)SK_MaxS32 / (double)SK_Fixed1)
            dfixdiv = SK_MinS32;
        else
            dfixdiv = SkFloatToFixed(dnumer / ddenom);
        diff = fixdiv - dfixdiv;

        if (SkAbs32(diff) > 1) {
            SkDebugf(" %d === numer %g denom %g div %g xdiv %x fxdiv %x\n",
                     i, dnumer, ddenom, ddiv, dfixdiv, fixdiv);
        }
        REPORTER_ASSERT(reporter, SkAbs32(diff) <= 1);
#endif
    }
#endif
}

#include "TestClassDef.h"
DEFINE_TESTCLASS("Sk64", Sk64TestClass, TestSk64)