/*  SPDX-License-Identifier: LGPL-2.0-or-later

    Copyright (C) 2009 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
    Copyright (C) 2007 Sebastian Pipping <webmaster@hartwork.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "kateregexp.h"

KateRegExp::KateRegExp(const QString &pattern, Qt::CaseSensitivity cs, QRegExp::PatternSyntax syntax)
    : m_regExp(pattern, cs, syntax)
{
}

// these things can besides '.' and '\s' make pattern multi-line:
// \n, \x000A, \x????-\x????, \0012, \0???-\0???
// a multi-line pattern must not pass as single-line, the other
// way around will just result in slower searches and is therefore
// not as critical
int KateRegExp::repairPattern(bool &stillMultiLine)
{
    const QString &text = pattern(); // read-only input for parsing

    // get input
    const int inputLen = text.length();
    int input = 0; // walker index

    // prepare output
    QString output;
    output.reserve(2 * inputLen + 1); // twice should be enough for the average case

    // parser state
    stillMultiLine = false;
    int replaceCount = 0;
    bool insideClass = false;

    while (input < inputLen) {
        if (insideClass) {
            // wait for closing, unescaped ']'
            switch (text[input].unicode()) {
            case L'\\':
                switch (text[input + 1].unicode()) {
                case L'x':
                    if (input + 5 < inputLen) {
                        // copy "\x????" unmodified
                        output.append(text.midRef(input, 6));
                        input += 6;
                    } else {
                        // copy "\x" unmodified
                        output.append(text.midRef(input, 2));
                        input += 2;
                    }
                    stillMultiLine = true;
                    break;

                case L'0':
                    if (input + 4 < inputLen) {
                        // copy "\0???" unmodified
                        output.append(text.midRef(input, 5));
                        input += 5;
                    } else {
                        // copy "\0" unmodified
                        output.append(text.midRef(input, 2));
                        input += 2;
                    }
                    stillMultiLine = true;
                    break;

                case L's':
                    // replace "\s" with "[ \t]"
                    output.append(QLatin1String(" \\t"));
                    input += 2;
                    replaceCount++;
                    break;

                case L'n':
                    stillMultiLine = true;
                    // FALLTROUGH
                    Q_FALLTHROUGH();
                default:
                    // copy "\?" unmodified
                    output.append(text.midRef(input, 2));
                    input += 2;
                }
                break;

            case L']':
                // copy "]" unmodified
                insideClass = false;
                output.append(text[input]);
                input++;
                break;

            default:
                // copy "?" unmodified
                output.append(text[input]);
                input++;
            }
        } else {
            // search for real dots and \S
            switch (text[input].unicode()) {
            case L'\\':
                switch (text[input + 1].unicode()) {
                case L'x':
                    if (input + 5 < inputLen) {
                        // copy "\x????" unmodified
                        output.append(text.midRef(input, 6));
                        input += 6;
                    } else {
                        // copy "\x" unmodified
                        output.append(text.midRef(input, 2));
                        input += 2;
                    }
                    stillMultiLine = true;
                    break;

                case L'0':
                    if (input + 4 < inputLen) {
                        // copy "\0???" unmodified
                        output.append(text.midRef(input, 5));
                        input += 5;
                    } else {
                        // copy "\0" unmodified
                        output.append(text.midRef(input, 2));
                        input += 2;
                    }
                    stillMultiLine = true;
                    break;

                case L's':
                    // replace "\s" with "[ \t]"
                    output.append(QLatin1String("[ \\t]"));
                    input += 2;
                    replaceCount++;
                    break;

                case L'n':
                    stillMultiLine = true;
                    // FALLTROUGH
                    Q_FALLTHROUGH();
                default:
                    // copy "\?" unmodified
                    output.append(text.midRef(input, 2));
                    input += 2;
                }
                break;

            case L'.':
                // replace " with "[^\n]"
                output.append(QLatin1String("[^\\n]"));
                input++;
                replaceCount++;
                break;

            case L'[':
                // copy "]" unmodified
                insideClass = true;
                output.append(text[input]);
                input++;
                break;

            default:
                // copy "?" unmodified
                output.append(text[input]);
                input++;
            }
        }
    }

    // Overwrite with repaired pattern
    m_regExp.setPattern(output);
    return replaceCount;
}

bool KateRegExp::isMultiLine() const
{
    const QString &text = pattern();

    // parser state
    bool insideClass = false;

    for (int input = 0; input < text.length(); /*empty*/) {
        if (insideClass) {
            // wait for closing, unescaped ']'
            switch (text[input].unicode()) {
            case L'\\':
                switch (text[input + 1].unicode()) {
                case L'x':
                    return true;

                case L'0':
                    return true;

                case L's':
                    // replace "\s" with "[ \t]"
                    input += 2;
                    break;

                case L'n':
                    return true;
                    // FALLTROUGH

                default:
                    // copy "\?" unmodified
                    input += 2;
                }
                break;

            case L']':
                // copy "]" unmodified
                insideClass = false;
                input++;
                break;

            default:
                // copy "?" unmodified
                input++;
            }
        } else {
            // search for real dots and \S
            switch (text[input].unicode()) {
            case L'\\':
                switch (text[input + 1].unicode()) {
                case L'x':
                    return true;

                case L'0':
                    return true;

                case L's':
                    // replace "\s" with "[ \t]"
                    input += 2;
                    break;

                case L'n':
                    return true;

                default:
                    // copy "\?" unmodified
                    input += 2;
                }
                break;

            case L'.':
                // replace " with "[^\n]"
                input++;
                break;

            case L'[':
                // copy "]" unmodified
                insideClass = true;
                input++;
                break;

            default:
                // copy "?" unmodified
                input++;
            }
        }
    }

    return false;
}

int KateRegExp::indexIn(const QString &str, int start, int end) const
{
    return m_regExp.indexIn(str.left(end), start, QRegExp::CaretAtZero);
}

int KateRegExp::lastIndexIn(const QString &str, int start, int end) const
{
    const int index = m_regExp.lastIndexIn(str.mid(start, end - start), -1, QRegExp::CaretAtZero);

    if (index == -1) {
        return -1;
    }

    const int index2 = m_regExp.indexIn(str.left(end), start + index, QRegExp::CaretAtZero);

    return index2;
}
