/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-7 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330,
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE


#include "juce_StringArray.h"


//==============================================================================
StringArray::StringArray() throw()
{
}

StringArray::StringArray (const StringArray& other) throw()
{
    addArray (other);
}

StringArray::StringArray (const juce_wchar** const strings,
                          const int numberOfStrings) throw()
{
    for (int i = 0; i < numberOfStrings; ++i)
        add (strings [i]);
}

StringArray::StringArray (const char** const strings,
                          const int numberOfStrings) throw()
{
    for (int i = 0; i < numberOfStrings; ++i)
        add (strings [i]);
}

StringArray::StringArray (const juce_wchar** const strings) throw()
{
    int i = 0;

    while (strings[i] != 0)
        add (strings [i++]);
}

StringArray::StringArray (const char** const strings) throw()
{
    int i = 0;

    while (strings[i] != 0)
        add (strings [i++]);
}

const StringArray& StringArray::operator= (const StringArray& other) throw()
{
    if (this != &other)
    {
        clear();
        addArray (other);
    }

    return *this;
}

StringArray::~StringArray() throw()
{
    clear();
}

bool StringArray::operator== (const StringArray& other) const throw()
{
    if (other.size() != size())
        return false;

    for (int i = size(); --i >= 0;)
    {
        if (*(String*) other.strings.getUnchecked(i)
               != *(String*) strings.getUnchecked(i))
        {
            return false;
        }
    }

    return true;
}

bool StringArray::operator!= (const StringArray& other) const throw()
{
    return ! operator== (other);
}

void StringArray::clear() throw()
{
    for (int i = size(); --i >= 0;)
    {
        String* const s = (String*) strings.getUnchecked(i);
        delete s;
    }

    strings.clear();
}

const String& StringArray::operator[] (const int index) const throw()
{
    if (((unsigned int) index) < (unsigned int) strings.size())
        return *(const String*) (strings.getUnchecked (index));

    return String::empty;
}

void StringArray::add (const String& newString) throw()
{
    strings.add (new String (newString));
}

void StringArray::insert (const int index,
                          const String& newString) throw()
{
    strings.insert (index, new String (newString));
}

void StringArray::addIfNotAlreadyThere (const String& newString,
                                        const bool ignoreCase) throw()
{
    if (! contains (newString, ignoreCase))
        add (newString);
}

void StringArray::addArray (const StringArray& otherArray,
                            int startIndex,
                            int numElementsToAdd) throw()
{
    if (startIndex < 0)
    {
        jassertfalse
        startIndex = 0;
    }

    if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size())
        numElementsToAdd = otherArray.size() - startIndex;

    while (--numElementsToAdd >= 0)
        strings.add (new String (*(const String*) otherArray.strings.getUnchecked (startIndex++)));
}

void StringArray::set (const int index,
                       const String& newString) throw()
{
    String* const s = (String*) strings [index];

    if (s != 0)
    {
        *s = newString;
    }
    else if (index >= 0)
    {
        add (newString);
    }
}

bool StringArray::contains (const String& stringToLookFor,
                            const bool ignoreCase) const throw()
{
    if (ignoreCase)
    {
        for (int i = size(); --i >= 0;)
            if (stringToLookFor.equalsIgnoreCase (*(const String*)(strings.getUnchecked(i))))
                return true;
    }
    else
    {
        for (int i = size(); --i >= 0;)
            if (stringToLookFor == *(const String*)(strings.getUnchecked(i)))
                return true;
    }

    return false;
}

int StringArray::indexOf (const String& stringToLookFor,
                          const bool ignoreCase,
                          int i) const throw()
{
    if (i < 0)
        i = 0;

    const int numElements = size();

    if (ignoreCase)
    {
        while (i < numElements)
        {
            if (stringToLookFor.equalsIgnoreCase (*(const String*) strings.getUnchecked (i)))
                return i;

            ++i;
        }
    }
    else
    {
        while (i < numElements)
        {
            if (stringToLookFor == *(const String*) strings.getUnchecked (i))
                return i;

            ++i;
        }
    }

    return -1;
}

//==============================================================================
void StringArray::remove (const int index) throw()
{
    String* const s = (String*) strings [index];

    if (s != 0)
    {
        strings.remove (index);
        delete s;
    }
}

void StringArray::removeString (const String& stringToRemove,
                                const bool ignoreCase) throw()
{
    if (ignoreCase)
    {
        for (int i = size(); --i >= 0;)
            if (stringToRemove.equalsIgnoreCase (*(const String*) strings.getUnchecked (i)))
                remove (i);
    }
    else
    {
        for (int i = size(); --i >= 0;)
            if (stringToRemove == *(const String*) strings.getUnchecked (i))
                remove (i);
    }
}

//==============================================================================
void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) throw()
{
    if (removeWhitespaceStrings)
    {
        for (int i = size(); --i >= 0;)
            if (! ((const String*) strings.getUnchecked(i))->containsNonWhitespaceChars())
                remove (i);
    }
    else
    {
        for (int i = size(); --i >= 0;)
            if (((const String*) strings.getUnchecked(i))->isEmpty())
                remove (i);
    }
}

void StringArray::trim() throw()
{
    for (int i = size(); --i >= 0;)
    {
        String& s = *(String*) strings.getUnchecked(i);
        s = s.trim();
    }
}

//==============================================================================
class InternalStringArrayComparator
{
public:
    static int compareElements (void* const first, void* const second) throw()
    {
        return ((const String*) first)->compare (*(const String*) second);
    }
};

class InsensitiveInternalStringArrayComparator
{
public:
    static int compareElements (void* const first, void* const second) throw()
    {
        return ((const String*) first)->compareIgnoreCase (*(const String*) second);
    }
};

void StringArray::sort (const bool ignoreCase) throw()
{
    if (ignoreCase)
    {
        InsensitiveInternalStringArrayComparator comp;
        strings.sort (comp);
    }
    else
    {
        InternalStringArrayComparator comp;
        strings.sort (comp);
    }
}

void StringArray::move (const int currentIndex, int newIndex) throw()
{
    strings.move (currentIndex, newIndex);
}


//==============================================================================
const String StringArray::joinIntoString (const String& separator,
                                          int start,
                                          int numberToJoin) const throw()
{
    const int last = (numberToJoin < 0) ? size()
                                        : jmin (size(), start + numberToJoin);

    if (start < 0)
        start = 0;

    if (start >= last)
        return String::empty;

    if (start == last - 1)
        return *(const String*) strings.getUnchecked (start);

    const int separatorLen = separator.length();
    int charsNeeded = separatorLen * (last - start - 1);

    for (int i = start; i < last; ++i)
        charsNeeded += ((const String*) strings.getUnchecked(i))->length();

    String result;
    result.preallocateStorage (charsNeeded);

    tchar* dest = (tchar*) (const tchar*) result;

    while (start < last)
    {
        const String& s = *(const String*) strings.getUnchecked (start);
        const int len = s.length();

        if (len > 0)
        {
            s.copyToBuffer (dest, len);
            dest += len;
        }

        if (++start < last && separatorLen > 0)
        {
            separator.copyToBuffer (dest, separatorLen);
            dest += separatorLen;
        }
    }

    *dest = 0;

    return result;
}

int StringArray::addTokens (const tchar* const text,
                            const bool preserveQuotedStrings) throw()
{
    return addTokens (text,
                      T(" \n\r\t"),
                      preserveQuotedStrings ? T("\"") : 0);
}

int StringArray::addTokens (const tchar* const text,
                            const tchar* breakCharacters,
                            const tchar* quoteCharacters) throw()
{
    int num = 0;

    if (text != 0 && *text != 0)
    {
        if (breakCharacters == 0)
            breakCharacters = T("");

        if (quoteCharacters == 0)
            quoteCharacters = T("");

        bool insideQuotes = false;
        tchar currentQuoteChar = 0;

        int i = 0;
        int tokenStart = 0;

        for (;;)
        {
            const tchar c = text[i];

            bool isBreak = (c == 0);

            if (! (insideQuotes || isBreak))
            {
                const tchar* b = breakCharacters;
                while (*b != 0)
                {
                    if (*b++ == c)
                    {
                        isBreak = true;
                        break;
                    }
                }
            }

            if (! isBreak)
            {
                bool isQuote = false;
                const tchar* q = quoteCharacters;
                while (*q != 0)
                {
                    if (*q++ == c)
                    {
                        isQuote = true;
                        break;
                    }
                }

                if (isQuote)
                {
                    if (insideQuotes)
                    {
                        // only break out of quotes-mode if we find a matching quote to the
                        // one that we opened with..
                        if (currentQuoteChar == c)
                            insideQuotes = false;
                    }
                    else
                    {
                        insideQuotes = true;
                        currentQuoteChar = c;
                    }
                }
            }
            else
            {
                add (String (text + tokenStart, i - tokenStart));

                ++num;
                tokenStart = i + 1;
            }

            if (c == 0)
                break;

            ++i;
        }
    }

    return num;
}

int StringArray::addLines (const tchar* text) throw()
{
    int numLines = 0;

    if (text != 0)
    {
        while (*text != 0)
        {
            const tchar* const startOfLine = text;

            while (*text != 0)
            {
                if (*text == T('\r'))
                {
                    ++text;
                    if (*text == T('\n'))
                        ++text;

                    break;
                }

                if (*text == T('\n'))
                {
                    ++text;
                    break;
                }

                ++text;
            }

            const tchar* endOfLine = text;
            if (endOfLine > startOfLine && (*(endOfLine - 1) == T('\r') || *(endOfLine - 1) == T('\n')))
                --endOfLine;

            if (endOfLine > startOfLine && (*(endOfLine - 1) == T('\r') || *(endOfLine - 1) == T('\n')))
                --endOfLine;

            add (String (startOfLine, jmax (0, (int) (endOfLine - startOfLine))));

            ++numLines;
        }
    }

    return numLines;
}

//==============================================================================
void StringArray::removeDuplicates (const bool ignoreCase) throw()
{
    for (int i = 0; i < size() - 1; ++i)
    {
        const String& s = *(String*) strings.getUnchecked(i);

        int nextIndex = i + 1;

        for (;;)
        {
            nextIndex = indexOf (s, ignoreCase, nextIndex);

            if (nextIndex < 0)
                break;

            remove (nextIndex);
        }
    }
}

void StringArray::appendNumbersToDuplicates (const bool ignoreCase,
                                             const bool appendNumberToFirstInstance,
                                             const tchar* const preNumberString,
                                             const tchar* const postNumberString) throw()
{
    for (int i = 0; i < size() - 1; ++i)
    {
        String& s = *(String*) strings.getUnchecked(i);

        int nextIndex = indexOf (s, ignoreCase, i + 1);

        if (nextIndex >= 0)
        {
            const String original (s);

            int number = 0;

            if (appendNumberToFirstInstance)
                s = original + preNumberString + String (++number) + postNumberString;
            else
                ++number;

            while (nextIndex >= 0)
            {
                set (nextIndex, (*this)[nextIndex] + preNumberString + String (++number) + postNumberString);
                nextIndex = indexOf (original, ignoreCase, nextIndex + 1);
            }
        }
    }
}

void StringArray::minimiseStorageOverheads() throw()
{
    strings.minimiseStorageOverheads();
}

END_JUCE_NAMESPACE
