// Written in the D programming language

module std.array;

private import std.c.stdio;
private import std.contracts;
private import std.traits;
private import std.gc;
private import std.string;
private import std.algorithm;
version(unittest) private import std.stdio;

class ArrayBoundsError : Error
{
  private:

    uint linnum;
    string filename;

  public:
    this(string filename, uint linnum)
    {
	this.linnum = linnum;
	this.filename = filename;

	char[] buffer = new char[19 + filename.length + linnum.sizeof * 3 + 1];
	auto len = sprintf(buffer.ptr,
                           "ArrayBoundsError %.*s(%u)", filename, linnum);
        buffer = buffer[0..len];
	super(assumeUnique(buffer)); // fine because buffer is unaliased
    }
}


/********************************************
 * Called by the compiler generated module assert function.
 * Builds an ArrayBoundsError exception and throws it.
 */

extern (C) static void _d_array_bounds(string filename, uint line)
{
    //printf("_d_assert(%s, %d)\n", (char *)filename, line);
    ArrayBoundsError a = new ArrayBoundsError(filename, line);
    //printf("assertion %p created\n", a);
    throw a;
}

/*
Returns an array consisting of $(D elements).

Example:

----
auto a = array(1, 2, 3);
assert(is(typeof(a) == int[]));
assert(a == [ 1, 2, 3 ]);
auto b = array(1, 2.2, 3);
assert(is(typeof(b) == double[]));
assert(b == [ 1.0, 2.2, 3 ]);
----
 */
// CommonType!(Ts)[] array(Ts...)(Ts elements)
// {
//     alias CommonType!(Ts) E;
//     alias typeof(return) R;
//     // 1. Allocate untyped memory
//     auto result = cast(E*) enforce(std.gc.malloc(elements.length * R.sizeof),
//             text("Out of memory while allocating an array of ",
//                     elements.length, " objects of type ", E.stringof));
//     // 2. Initialize the memory
//     size_t constructedElements = 0;
//     scope(failure)
//     {
//         // Deconstruct only what was constructed
//         foreach_reverse (i; 0 .. constructedElements)
//         {
//             try
//             {
//                 //result[i].~E();
//             }
//             catch (Exception e)
//             {
//             }
//         }
//         // free the entire array
//         std.gc.realloc(result, 0);
//     }
//     foreach (src; elements)
//     {
//         static if (is(typeof(new(result + constructedElements) E(src))))
//         {
//             new(result + constructedElements) E(src);
//         }
//         else
//         {
//             result[constructedElements] = src;
//         }
//         ++constructedElements;
//     }
//     // 3. Success constructing all elements, type the array and return it
//     setTypeInfo(typeid(E), result);
//     return result[0 .. constructedElements];
// }

// unittest
// {
//     auto a = array(1, 2, 3, 4, 5);
//     writeln(a);
//     assert(a == [ 1, 2, 3, 4, 5 ]);

//     struct S { int x; string toString() { return .toString(x); } }
//     auto b = array(S(1), S(2));
//     writeln(b);

//     class C
//     {
//         int x;
//         this(int y) { x = y; }
//         string toString() { return .toString(x); }
//     }
//     auto c = array(new C(1), new C(2));
//     writeln(c);

//     auto d = array(1, 2.2, 3);
//     assert(is(typeof(d) == double[]));
//     writeln(d);
// }

template IndexType(C : T[], T)
{
    alias size_t IndexType;
}

unittest
{
    static assert(is(IndexType!(double[]) == size_t));
    static assert(!is(IndexType!(double) == size_t));
}

/**
Inserts $(D stuff) in $(D container) at position $(D pos).
 */
void insert(T, Range)(ref T[] array, size_t pos, Range stuff)
{
    static if (is(typeof(stuff[0])))
    {
        // presumably an array
        alias stuff toInsert;
        assert(!overlap(array, toInsert));
    }
    else
    {
        // presumably only one element
        auto toInsert = (&stuff)[0 .. 1];
    }

    // @@@BUG 2130@@@
    // invariant
    //     size_t delta = toInsert.length,
    //     size_t oldLength = array.length,
    //     size_t newLength = oldLength + delta;
    invariant
        delta = toInsert.length,
        oldLength = array.length,
        newLength = oldLength + delta;

    // Reallocate the array to make space for new content
    array = cast(T[]) realloc(array.ptr, newLength * array[0].sizeof);
    assert(array.length == newLength);

    // Move data in pos .. pos + stuff.length to the end of the array
    foreach_reverse (i; pos .. oldLength)
    {
        // This will be guaranteed to not throw
        move(array[i], array[i + delta]);
    }

    // Copy stuff into array
    foreach (e; toInsert)
    {
        array[pos++] = e;
    }
}

unittest
{
    int[] a = ([1, 4, 5]).dup;
    insert(a, 1u, [2, 3]);
    assert(a == [1, 2, 3, 4, 5]);
    insert(a, 1u, 99);
    assert(a == [1, 99, 2, 3, 4, 5]);
}

/**
Erases elements from $(D array) with indices ranging from $(D from)
(inclusive) to $(D to) (exclusive).
 */
void erase(T)(ref T[] array, size_t from, size_t to)
{
    invariant newLength = array.length - (to - from);
    foreach (i; to .. array.length)
    {
        move(array[i], array[from++]);
    }
    array.length = newLength;
}

unittest
{
    int[] a = [1, 2, 3, 4, 5];
    erase(a, 1u, 3u);
    assert(a == [1, 4, 5]);
}

/**
Replaces elements from $(D array) with indices ranging from $(D from)
(inclusive) to $(D to) (exclusive) with the range $(D stuff). Expands
or shrinks the array as needed.
 */
void replace(T, Range)(ref T[] array, size_t from, size_t to,
        Range stuff)
{
    // container = container[0 .. from] ~ stuff ~ container[to .. $];
    if (overlap(array, stuff))
    {
        // use slower/conservative method
        array = array[0 .. from] ~ stuff ~ array[to .. $];
    }
    else if (stuff.length <= to - from)
    {
        // replacement reduces length
        // BUG 2128
        //invariant stuffEnd = from + stuff.length;
        auto stuffEnd = from + stuff.length;
        array[from .. stuffEnd] = stuff;
        erase(array, stuffEnd, to);
    }
    else
    {
        // replacement increases length
        // @@@TODO@@@: optimize this
        invariant replaceLen = to - from;
        array[from .. to] = stuff[0 .. replaceLen];
        insert(array, to, stuff[replaceLen .. $]);
    }
}

unittest
{
    int[] a = [1, 4, 5];
    replace(a, 1u, 2u, [2, 3, 4]);
    assert(a == [1, 2, 3, 4, 5]);
}

