#include "il2cpp-config.h"
#include <memory>
#include "il2cpp-api.h"
#include "il2cpp-object-internals.h"
#include "il2cpp-class-internals.h"
#include "icalls/mscorlib/System/String.h"
#include "utils/StringUtils.h"
#include "vm/Array.h"
#include "vm/Class.h"
#include "vm/String.h"
#include "vm/Exception.h"

namespace il2cpp
{
namespace icalls
{
namespace mscorlib
{
namespace System
{
    void String::RedirectToCreateString()
    {
        NOT_SUPPORTED_IL2CPP(String::RedirectToCreateString, "All String constructors should be redirected to String.CreateString.");
    }

    Il2CppString * String::InternalAllocateStr(int length)
    {
        return il2cpp::vm::String::NewSize(length);
    }

    static bool
    string_icall_is_in_array(Il2CppArray *chars, int32_t arraylength, Il2CppChar chr)
    {
        Il2CppChar cmpchar;
        int32_t arrpos;

        for (arrpos = 0; arrpos != arraylength; arrpos++)
        {
            cmpchar = il2cpp_array_get(chars, Il2CppChar, arrpos);
            if (cmpchar == chr)
                return true;
        }

        return false;
    }

/* System.StringSplitOptions */
    typedef enum
    {
        STRINGSPLITOPTIONS_NONE = 0,
        STRINGSPLITOPTIONS_REMOVE_EMPTY_ENTRIES = 1
    } StringSplitOptions;

    Il2CppArray * String::InternalSplit(Il2CppString* me, Il2CppArray* separator, int count, int options)
    {
        static Il2CppClass *String_array;
        Il2CppString * tmpstr;
        Il2CppArray * retarr;
        Il2CppChar *src;
        int32_t arrsize, srcsize, splitsize;
        int32_t i, lastpos, arrpos;
        int32_t tmpstrsize;
        int32_t remempty;
        int32_t flag;
        Il2CppChar *tmpstrptr;

        remempty = options & STRINGSPLITOPTIONS_REMOVE_EMPTY_ENTRIES;
        src = il2cpp::utils::StringUtils::GetChars(me);
        srcsize = il2cpp::utils::StringUtils::GetLength(me);
        arrsize = il2cpp::vm::Array::GetLength(separator);

        if (!String_array)
        {
            Il2CppClass *klass = il2cpp::vm::Class::GetArrayClass(il2cpp_defaults.string_class, 1);
            //mono_memory_barrier ();
            String_array = klass;
        }

        splitsize = 1;
        /* Count the number of elements we will return. Note that this operation
         * guarantees that we will return exactly splitsize elements, and we will
         * have enough data to fill each. This allows us to skip some checks later on.
         */
        if (remempty == 0)
        {
            for (i = 0; i != srcsize && splitsize < count; i++)
            {
                if (string_icall_is_in_array(separator, arrsize, src[i]))
                    splitsize++;
            }
        }
        else if (count > 1)
        {
            /* Require pattern "Nondelim + Delim + Nondelim" to increment counter.
             * Lastpos != 0 means first nondelim found.
             * Flag = 0 means last char was delim.
             * Efficient, though perhaps confusing.
             */
            lastpos = 0;
            flag = 0;
            for (i = 0; i != srcsize && splitsize < count; i++)
            {
                if (string_icall_is_in_array(separator, arrsize, src[i]))
                {
                    flag = 0;
                }
                else if (flag == 0)
                {
                    if (lastpos == 1)
                        splitsize++;
                    flag = 1;
                    lastpos = 1;
                }
            }

            /* Nothing but separators */
            if (lastpos == 0)
            {
                retarr = il2cpp::vm::Array::NewSpecific(String_array, 0);
                return retarr;
            }
        }

        /* if no split chars found return the string */
        if (splitsize == 1)
        {
            if (remempty == 0 || count == 1)
            {
                /* Copy the whole string */
                retarr = il2cpp::vm::Array::NewSpecific(String_array, 1);
                il2cpp_array_setref(retarr, 0, me);
            }
            else
            {
                /* otherwise we have to filter out leading & trailing delims */

                /* find first non-delim char */
                for (; srcsize != 0; srcsize--, src++)
                {
                    if (!string_icall_is_in_array(separator, arrsize, src[0]))
                        break;
                }
                /* find last non-delim char */
                for (; srcsize != 0; srcsize--)
                {
                    if (!string_icall_is_in_array(separator, arrsize, src[srcsize - 1]))
                        break;
                }
                tmpstr = il2cpp::vm::String::NewSize(srcsize);
                tmpstrptr = il2cpp::utils::StringUtils::GetChars(tmpstr);

                memcpy(tmpstrptr, src, srcsize * sizeof(Il2CppChar));
                retarr = il2cpp::vm::Array::NewSpecific(String_array, 1);
                il2cpp_array_setref(retarr, 0, tmpstr);
            }
            return retarr;
        }

        lastpos = 0;
        arrpos = 0;

        retarr = il2cpp::vm::Array::NewSpecific(String_array, splitsize);

        for (i = 0; i != srcsize && arrpos != splitsize; i++)
        {
            if (string_icall_is_in_array(separator, arrsize, src[i]))
            {
                if (lastpos != i || remempty == 0)
                {
                    tmpstrsize = i - lastpos;
                    tmpstr = il2cpp::vm::String::NewSize(tmpstrsize);
                    tmpstrptr = il2cpp::utils::StringUtils::GetChars(tmpstr);

                    memcpy(tmpstrptr, src + lastpos, tmpstrsize * sizeof(Il2CppChar));
                    il2cpp_array_setref(retarr, arrpos, tmpstr);
                    arrpos++;

                    if (arrpos == splitsize - 1)
                    {
                        /* Shortcut the last array element */

                        lastpos = i + 1;
                        if (remempty != 0)
                        {
                            /* Search for non-delim starting char (guaranteed to find one) Note that loop
                             * condition is only there for safety. It will never actually terminate the loop. */
                            for (; lastpos != srcsize; lastpos++)
                            {
                                if (!string_icall_is_in_array(separator, arrsize, src[lastpos]))
                                    break;
                            }
                            if (count > splitsize)
                            {
                                /* Since we have fewer results than our limit, we must remove
                                 * trailing delimiters as well.
                                 */
                                for (; srcsize != lastpos + 1; srcsize--)
                                {
                                    if (!string_icall_is_in_array(separator, arrsize, src[srcsize - 1]))
                                        break;
                                }
                            }
                        }

                        tmpstrsize = srcsize - lastpos;
                        tmpstr = il2cpp::vm::String::NewSize(tmpstrsize);
                        tmpstrptr = il2cpp::utils::StringUtils::GetChars(tmpstr);

                        memcpy(tmpstrptr, src + lastpos, tmpstrsize * sizeof(Il2CppChar));
                        il2cpp_array_setref(retarr, arrpos, tmpstr);

                        /* Loop will ALWAYS end here. Test criteria in the FOR loop is technically unnecessary. */
                        break;
                    }
                }
                lastpos = i + 1;
            }
        }

        return retarr;
    }

    Il2CppString* String::InternalIntern(Il2CppString* str)
    {
        return il2cpp_string_intern(str);
    }

    Il2CppString* String::InternalIsInterned(Il2CppString* str)
    {
        return il2cpp_string_is_interned(str);
    }

#if NET_4_0
    Il2CppString* String::FastAllocateString(int32_t length)
    {
        return vm::String::NewSize(length);
    }

#endif
} /* namespace System */
} /* namespace mscorlib */
} /* namespace icalls */
} /* namespace il2cpp */
