
#include <cmath>
#include <cstdlib>

#ifdef HPCG_CUBIC_RADICAL_SEARCH
#include <algorithm>
#endif
#include <map>

#include "ComputeOptimalShapeXYZ.hpp"
#include "MixedBaseCounter.hpp"

#ifdef HPCG_CUBIC_RADICAL_SEARCH
static int min3(int a, int b, int c)
{
    return std::min(a, std::min(b, c));
}

static int max3(int a, int b, int c)
{
    return std::max(a, std::max(b, c));
}

static void cubic_radical_search(int n, int& x, int& y, int& z)
{
    double best = 0.0;

    for (int f1 = (int) (pow(n, 1.0 / 3.0) + 0.5); f1 > 0; --f1)
        if (n % f1 == 0)
        {
            int n1 = n / f1;
            for (int f2 = (int) (pow(n1, 0.5) + 0.5); f2 > 0; --f2)
                if (n1 % f2 == 0)
                {
                    int f3 = n1 / f2;
                    double current = (double) min3(f1, f2, f3) / max3(f1, f2, f3);
                    if (current > best)
                    {
                        best = current;
                        x = f1;
                        y = f2;
                        z = f3;
                    }
                }
        }
}

#else

static void ComputePrimeFactors(int n, std::map<int, int>& factors)
{
    int d, sq = int((sqrt(double(n))) + 1L);
    div_t r;

    // remove 2 as a factor with shifts instead "/" and "%"
    for (; n > 1 && (n & 1) == 0; n >>= 1)
    {
        factors[2]++;
    }

    // keep removing subsequent odd numbers
    for (d = 3; d <= sq; d += 2)
    {
        while (1)
        {
            r = div(n, d);
            if (r.rem == 0)
            {
                factors[d]++;
                n = r.quot;
                continue;
            }
            break;
        }
    }
    if (n > 1 || factors.size() == 0) // left with a prime or x==1
        factors[n]++;
}

static int pow_i(int x, int p)
{
    int v;

    if (0 == x || 1 == x)
        return x;

    if (p < 0)
        return 0;

    for (v = 1; p; p >>= 1)
    {
        if (1 & p)
            v *= x;
        x *= x;
    }

    return v;
}

#endif

void ComputeOptimalShapeXYZ(int xyz, int& x, int& y, int& z)
{
#ifdef HPCG_CUBIC_RADICAL_SEARCH
    cubic_radical_search(xyz, x, y, z);
#else
    std::map<int, int> factors;

    ComputePrimeFactors(xyz, factors); // factors are sorted: ascending order

    std::map<int, int>::iterator iter = factors.begin();

    // there is at least one prime factor
    x = (iter++)->first; // cache the first factor, move to the next one

    y = iter != factors.end() ? (iter++)->first : y; // try to cache the second factor in "y"

    if (factors.size() == 1)
    { // only a single factor
        z = pow_i(x, factors[x] / 3);
        y = pow_i(x, factors[x] / 3 + ((factors[x] % 3) >= 2 ? 1 : 0));
        x = pow_i(x, factors[x] / 3 + ((factors[x] % 3) >= 1 ? 1 : 0));
    }
    else if (factors.size() == 2 && factors[x] == 1 && factors[y] == 1)
    { // two distinct prime factors
        z = 1;
    }
    else if (factors.size() == 2 && factors[x] + factors[y] == 3)
    {                                // three prime factors, one repeated
        z = factors[x] == 2 ? x : y; // test which factor is repeated
    }
    else if (factors.size() == 3 && factors[x] == 1 && factors[y] == 1 && iter->second == 1)
    { // three distinct and single prime factors
        z = iter->first;
    }
    else
    { // 3 or more prime factors so try all possible 3-subsets

        int i, distinct_factors[32 + 1], count_factors[32 + 1];

        i = 0;
        for (std::map<int, int>::iterator iter = factors.begin(); iter != factors.end(); ++iter, ++i)
        {
            distinct_factors[i] = iter->first;
            count_factors[i] = iter->second;
        }

        // count total number of prime factors in "c_main" and distribute some factors into "c1"
        MixedBaseCounter c_main(count_factors, factors.size()), c1(count_factors, factors.size());

        // at the beginning, minimum area is the maximum area
        double area, min_area = 2.0 * xyz + 1.0;

        for (c1.next(); !c1.is_zero(); c1.next())
        {
            MixedBaseCounter c2(c_main, c1); // "c2" gets the factors remaining in "c_main" that "c1" doesn't have
            for (c2.next(); !c2.is_zero(); c2.next())
            {
                int tf1 = c1.product(distinct_factors);
                int tf2 = c2.product(distinct_factors);
                int tf3 = xyz / tf1 / tf2; // we derive the third dimension, we don't keep track of the factors it has

                area = tf1 * double(tf2) + tf2 * double(tf3) + tf1 * double(tf3);
                if (area < min_area)
                {
                    min_area = area;
                    x = tf1;
                    y = tf2;
                    z = tf3;
                }
            }
        }
    }
#endif
}
