/* Copyright (C) 2011-2022 by The D Language Foundation, All Rights Reserved
 * written by Walter Bright
 * https://www.digitalmars.com
 * Distributed under the Boost Software License, Version 1.0.
 * https://www.boost.org/LICENSE_1_0.txt
 * https://github.com/dlang/dmd/blob/master/src/dmd/root/array.h
 */

#pragma once

#include "dsystem.h"
#include "object.h"
#include "rmem.h"

template <typename TYPE>
struct Array
{
    d_size_t length;

  private:
    DArray<TYPE> data;
    #define SMALLARRAYCAP       1
    TYPE smallarray[SMALLARRAYCAP];    // inline storage for small arrays

    Array(const Array&);

  public:
    Array()
    {
        data.ptr = NULL;
        length = 0;
        data.length = 0;
    }

    ~Array()
    {
        if (data.ptr != &smallarray[0])
            mem.xfree(data.ptr);
    }

    char *toChars() const
    {
        const char **buf = (const char **)mem.xmalloc(length * sizeof(const char *));
        d_size_t len = 2;
        for (d_size_t u = 0; u < length; u++)
        {
            buf[u] = ((RootObject *)data.ptr[u])->toChars();
            len += strlen(buf[u]) + 1;
        }
        char *str = (char *)mem.xmalloc(len);

        str[0] = '[';
        char *p = str + 1;
        for (d_size_t u = 0; u < length; u++)
        {
            if (u)
                *p++ = ',';
            len = strlen(buf[u]);
            memcpy(p,buf[u],len);
            p += len;
        }
        *p++ = ']';
        *p = 0;
        mem.xfree(buf);
        return str;
    }

    void push(TYPE ptr)
    {
        reserve(1);
        data.ptr[length++] = ptr;
    }

    void append(Array *a)
    {
        insert(length, a);
    }

    void reserve(d_size_t nentries)
    {
        //printf("Array::reserve: length = %d, data.length = %d, nentries = %d\n", (int)length, (int)data.length, (int)nentries);
        if (data.length - length < nentries)
        {
            if (data.length == 0)
            {
                // Not properly initialized, someone memset it to zero
                if (nentries <= SMALLARRAYCAP)
                {
                    data.length = SMALLARRAYCAP;
                    data.ptr = SMALLARRAYCAP ? &smallarray[0] : NULL;
                }
                else
                {
                    data.length = nentries;
                    data.ptr = (TYPE *)mem.xmalloc(data.length * sizeof(TYPE));
                }
            }
            else if (data.length == SMALLARRAYCAP)
            {
                data.length = length + nentries;
                data.ptr = (TYPE *)mem.xmalloc(data.length * sizeof(TYPE));
                memcpy(data.ptr, &smallarray[0], length * sizeof(TYPE));
            }
            else
            {
                /* Increase size by 1.5x to avoid excessive memory fragmentation
                 */
                d_size_t increment = length / 2;
                if (nentries > increment)       // if 1.5 is not enough
                    increment = nentries;
                data.length = length + increment;
                data.ptr = (TYPE *)mem.xrealloc(data.ptr, data.length * sizeof(TYPE));
            }
        }
    }

    void remove(d_size_t i)
    {
        if (length - i - 1)
            memmove(data.ptr + i, data.ptr + i + 1, (length - i - 1) * sizeof(TYPE));
        length--;
    }

    void insert(d_size_t index, Array *a)
    {
        if (a)
        {
            d_size_t d = a->length;
            reserve(d);
            if (length != index)
                memmove(data.ptr + index + d, data.ptr + index, (length - index) * sizeof(TYPE));
            memcpy(data.ptr + index, a->data.ptr, d * sizeof(TYPE));
            length += d;
        }
    }

    void insert(d_size_t index, TYPE ptr)
    {
        reserve(1);
        memmove(data.ptr + index + 1, data.ptr + index, (length - index) * sizeof(TYPE));
        data.ptr[index] = ptr;
        length++;
    }

    void setDim(d_size_t newdim)
    {
        if (length < newdim)
        {
            reserve(newdim - length);
        }
        length = newdim;
    }

    d_size_t find(TYPE ptr) const
    {
        for (d_size_t i = 0; i < length; i++)
        {
            if (data.ptr[i] == ptr)
                return i;
        }
        return SIZE_MAX;
    }

    bool contains(TYPE ptr) const
    {
        return find(ptr) != SIZE_MAX;
    }

    TYPE& operator[] (d_size_t index)
    {
#ifdef DEBUG
        assert(index < length);
#endif
        return data.ptr[index];
    }

    TYPE *tdata()
    {
        return data.ptr;
    }

    Array *copy()
    {
        Array *a = new Array();
        a->setDim(length);
        memcpy(a->data.ptr, data.ptr, length * sizeof(TYPE));
        return a;
    }

    void shift(TYPE ptr)
    {
        reserve(1);
        memmove(data.ptr + 1, data.ptr, length * sizeof(TYPE));
        data.ptr[0] = ptr;
        length++;
    }

    void zero()
    {
        memset(data.ptr, 0, length * sizeof(TYPE));
    }

    TYPE pop()
    {
        return data.ptr[--length];
    }
};
