/**
 * Contains support code for switch blocks using string constants.
 *
 * Copyright: Copyright Digital Mars 2004 - 2010.
 * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Walter Bright, Sean Kelly
 */

/*          Copyright Digital Mars 2004 - 2010.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
module rt.switch_;

private import core.stdc.string;

/******************************************************
 * Support for switch statements switching on strings.
 * Input:
 *      table[]         sorted array of strings generated by compiler
 *      ca              string to look up in table
 * Output:
 *      result          index of match in table[]
 *                      -1 if not in table
 */

extern (C):

int _d_switch_string(char[][] table, char[] ca)
in
{
    //printf("in _d_switch_string()\n");
    assert(table.length >= 0);
    assert(ca.length >= 0);

    // Make sure table[] is sorted correctly
    for (size_t j = 1u; j < table.length; j++)
    {
        auto len1 = table[j - 1].length;
        auto len2 = table[j].length;

        assert(len1 <= len2);
        if (len1 == len2)
        {
            int ci;

            ci = memcmp(table[j - 1].ptr, table[j].ptr, len1);
            assert(ci < 0); // ci==0 means a duplicate
        }
    }
}
out (result)
{
    int cj;

    //printf("out _d_switch_string()\n");
    if (result == -1)
    {
        // Not found
        for (auto i = 0u; i < table.length; i++)
        {
            if (table[i].length == ca.length)
            {   cj = memcmp(table[i].ptr, ca.ptr, ca.length);
                assert(cj != 0);
            }
        }
    }
    else
    {
        assert(0 <= result && cast(size_t)result < table.length);
        for (auto i = 0u; 1; i++)
        {
            assert(i < table.length);
            if (table[i].length == ca.length)
            {
                cj = memcmp(table[i].ptr, ca.ptr, ca.length);
                if (cj == 0)
                {
                    assert(i == result);
                    break;
                }
            }
        }
    }
}
body
{
    //printf("body _d_switch_string(%.*s)\n", ca.length, ca.ptr);
    size_t low  = 0;
    size_t high = table.length;

    version (none)
    {
        // Print table
        printf("ca[] = '%s'\n", ca.length, ca.ptr);
        for (auto i = 0; i < high; i++)
        {
            auto pca = table[i];
            printf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr);
        }
    }
    if (high &&
        ca.length >= table[0].length &&
        ca.length <= table[high - 1].length)
    {
        // Looking for 0 length string, which would only be at the beginning
        if (ca.length == 0)
            return 0;

        char c1 = ca[0];

        // Do binary search
        while (low < high)
        {
            auto mid = (low + high) >> 1;
            auto pca = table[mid];
            auto c   = cast(sizediff_t)(ca.length - pca.length);
            if (c == 0)
            {
                c = cast(ubyte)c1 - cast(ubyte)pca[0];
                if (c == 0)
                {
                    c = memcmp(ca.ptr, pca.ptr, ca.length);
                    if (c == 0)
                    {   //printf("found %d\n", mid);
                        return cast(int)mid;
                    }
                }
            }
            if (c < 0)
            {
                high = mid;
            }
            else
            {
                low = mid + 1;
            }
        }
    }

    //printf("not found\n");
    return -1; // not found
}

unittest
{
    switch (cast(char []) "c")
    {
         case "coo":
         default:
             break;
    }

    int bug5381(string s)
    {
        switch (s)
        {
            case "unittest":        return 1;
            case "D_Version2":      return 2;
            case "none":            return 3;
            case "all":             return 4;
            default:                return 5;
        }
    }
    int rc = bug5381("none");
    assert(rc == 3);
}

/**********************************
 * Same thing, but for wide chars.
 */

int _d_switch_ustring(wchar[][] table, wchar[] ca)
in
{
    //printf("in _d_switch_ustring()\n");
    assert(table.length >= 0);
    assert(ca.length >= 0);

    // Make sure table[] is sorted correctly
    for (size_t j = 1u; j < table.length; j++)
    {
        auto len1 = table[j - 1].length;
        auto len2 = table[j].length;

        assert(len1 <= len2);
        if (len1 == len2)
        {
            int c;

            c = memcmp(table[j - 1].ptr, table[j].ptr, len1 * wchar.sizeof);
            assert(c < 0);  // c==0 means a duplicate
        }
    }
}
out (result)
{
    int c;

    //printf("out _d_switch_ustring()\n");
    if (result == -1)
    {
        // Not found
        for (auto i = 0u; i < table.length; i++)
        {
            if (table[i].length == ca.length)
            {   c = memcmp(table[i].ptr, ca.ptr, ca.length * wchar.sizeof);
                assert(c != 0);
            }
        }
    }
    else
    {
        assert(0 <= result && cast(size_t)result < table.length);
        for (auto i = 0u; 1; i++)
        {
            assert(i < table.length);
            if (table[i].length == ca.length)
            {
                c = memcmp(table[i].ptr, ca.ptr, ca.length * wchar.sizeof);
                if (c == 0)
                {
                    assert(i == result);
                    break;
                }
            }
        }
    }
}
body
{
    //printf("body _d_switch_ustring()\n");
    size_t low = 0;
    auto high = table.length;

    version (none)
    {
        // Print table
        wprintf("ca[] = '%.*s'\n", ca.length, ca.ptr);
        for (auto i = 0; i < high; i++)
        {
            auto pca = table[i];
            wprintf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr);
        }
    }

    // Do binary search
    while (low < high)
    {
        auto mid = (low + high) >> 1;
        auto pca = table[mid];
        auto c = cast(sizediff_t)(ca.length - pca.length);
        if (c == 0)
        {
            c = memcmp(ca.ptr, pca.ptr, ca.length * wchar.sizeof);
            if (c == 0)
            {   //printf("found %d\n", mid);
                return cast(int)mid;
            }
        }
        if (c < 0)
        {
            high = mid;
        }
        else
        {
            low = mid + 1;
        }
    }
    //printf("not found\n");
    return -1;              // not found
}


unittest
{
    switch (cast(wchar []) "c")
    {
         case "coo":
         default:
             break;
    }

    int bug5381(wstring ws)
    {
        switch (ws)
        {
            case "unittest":        return 1;
            case "D_Version2":      return 2;
            case "none":            return 3;
            case "all":             return 4;
            default:                return 5;
        }
    }
    int rc = bug5381("none"w);
    assert(rc == 3);
}

/**********************************
 * Same thing, but for wide chars.
 */

int _d_switch_dstring(dchar[][] table, dchar[] ca)
in
{
    //printf("in _d_switch_dstring()\n");
    assert(table.length >= 0);
    assert(ca.length >= 0);

    // Make sure table[] is sorted correctly
    for (auto j = 1u; j < table.length; j++)
    {
        auto len1 = table[j - 1].length;
        auto len2 = table[j].length;

        assert(len1 <= len2);
        if (len1 == len2)
        {
            auto c = memcmp(table[j - 1].ptr, table[j].ptr, len1 * dchar.sizeof);
            assert(c < 0);  // c==0 means a duplicate
        }
    }
}
out (result)
{
    //printf("out _d_switch_dstring()\n");
    if (result == -1)
    {
        // Not found
        for (auto i = 0u; i < table.length; i++)
        {
            if (table[i].length == ca.length)
            {   auto c = memcmp(table[i].ptr, ca.ptr, ca.length * dchar.sizeof);
                assert(c != 0);
            }
        }
    }
    else
    {
        assert(0 <= result && cast(size_t)result < table.length);
        for (auto i = 0u; 1; i++)
        {
            assert(i < table.length);
            if (table[i].length == ca.length)
            {
                auto c = memcmp(table[i].ptr, ca.ptr, ca.length * dchar.sizeof);
                if (c == 0)
                {
                    assert(i == result);
                    break;
                }
            }
        }
    }
}
body
{
    //printf("body _d_switch_dstring()\n");
    size_t low = 0;
    auto high = table.length;

    version (none)
    {
        // Print table
        wprintf("ca[] = '%.*s'\n", ca.length, ca.ptr);
        for (auto i = 0; i < high; i++)
        {
            auto pca = table[i];
            wprintf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr);
        }
    }

    // Do binary search
    while (low < high)
    {
        auto mid = (low + high) >> 1;
        auto pca = table[mid];
        auto c = cast(sizediff_t)(ca.length - pca.length);
        if (c == 0)
        {
            c = memcmp(ca.ptr, pca.ptr, ca.length * dchar.sizeof);
            if (c == 0)
            {   //printf("found %d\n", mid);
                return cast(int)mid;
            }
        }
        if (c < 0)
        {
            high = mid;
        }
        else
        {
            low = mid + 1;
        }
    }
    //printf("not found\n");
    return -1; // not found
}


unittest
{
    switch (cast(dchar []) "c")
    {
         case "coo":
         default:
             break;
    }

    int bug5381(dstring ds)
    {
        switch (ds)
        {
            case "unittest":        return 1;
            case "D_Version2":      return 2;
            case "none":            return 3;
            case "all":             return 4;
            default:                return 5;
        }
    }
    int rc = bug5381("none"d);
    assert(rc == 3);
}
