/* This file is part of the 'stringi' project.
 * Copyright (c) 2013-2024, Marek Gagolewski <https://www.gagolewski.com/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#ifndef __stri_brkiter_h
#define __stri_brkiter_h

#include "stri_stringi.h"
#include <deque>
#include <utility>
#include <vector>
#include <unicode/brkiter.h>
#include <unicode/uloc.h>
#include <unicode/locid.h>

/**
 * A class to manage a break iterator's options
 *
 * @version 0.4-1 (Marek Gagolewski, 2014-12-02)
 *
 * @version 1.1.3 (Marek Gagolewski, 2017-01-07) UBRK_COUNT deprecated
 *
 * @version 1.1.6 (Marek Gagolewski, 2017-04-22) Add support for RBBI
 */
class StriBrkIterOptions {
protected:

    const char* locale;      // R_alloc'd
    UnicodeString rules;
    UBreakIteratorType type;
    int32_t* skip_rules;     // R_alloc'd
    R_len_t  skip_size;      // number of elements in skip_rules


private:

    void setEmptyOpts()
    {
        locale = NULL;
        type = UBRK_CHARACTER;
        skip_rules = NULL;
        skip_size = 0;
    }

    void setType(SEXP opts_brkiter, const char* default_type);
    void setLocale(SEXP opts_brkiter);
    void setSkipRuleStatus(SEXP opts_brkiter);


public:


    StriBrkIterOptions() {
        setEmptyOpts();
    }

    StriBrkIterOptions(SEXP opts_brkiter, const char* default_type) {
        setEmptyOpts();
        setLocale(opts_brkiter);
        setSkipRuleStatus(opts_brkiter);
        setType(opts_brkiter, default_type);
    }
};


/**
 * A class to manage a break iterator
 *
 * @version 0.3-1 (Marek Gagolewski, 2014-10-30)
 *
 * @version 0.4-1 (Marek Gagolewski, 2014-12-02) separate class
 *
 * @version 1.1.3 (Marek Gagolewski, 2017-01-07) UBRK_COUNT deprecated
 *
 * @version 1.1.6 (Marek Gagolewski, 2017-04-22) Add support for RBBI
 *
 * @version 1.8.1 (Marek Gagolewski, 2023-11-09)
 *     warn if resource bundle for an explicitly set locale is unavailable
 */
class StriUBreakIterator : public StriBrkIterOptions {
private:

    UBreakIterator* uiterator;

    void open() {
#ifndef NDEBUG
        if (uiterator) throw StriException("!NDEBUG: StriUBreakIterator::open()");
#endif
        UErrorCode status = U_ZERO_ERROR;
        if (!rules.isEmpty()) {
            UParseError parseErr;
            uiterator = ubrk_openRules(rules.getTerminatedBuffer(),
                                       -1/*null-terminated*/, NULL, 0,
                                       &parseErr, &status);
        }
        else {
            switch (type) {
            case UBRK_CHARACTER: // character
                uiterator = ubrk_open(UBRK_CHARACTER, locale, NULL, 0, &status);
                break;
            case UBRK_LINE: // line_break
                uiterator = ubrk_open(UBRK_LINE, locale, NULL, 0, &status);
                break;
            case UBRK_SENTENCE: // sentence
                uiterator = ubrk_open(UBRK_SENTENCE, locale, NULL, 0, &status);
                break;
            case UBRK_WORD: // word
                uiterator = ubrk_open(UBRK_WORD, locale, NULL, 0, &status);
                break;
            default:
                throw StriException(MSG__INTERNAL_ERROR);
            }
        }
        STRI__CHECKICUSTATUS_THROW(status, {/* do nothing special on err */})

        if (status == U_USING_DEFAULT_WARNING && uiterator && locale) {
            UErrorCode status2 = U_ZERO_ERROR;
            const char* valid_locale = ubrk_getLocaleByType(uiterator, ULOC_VALID_LOCALE, &status2);
            if (valid_locale && !strcmp(valid_locale, "root"))
                Rf_warning("%s", ICUError::getICUerrorName(status));
        }
    }


public:

    StriUBreakIterator()
        : StriBrkIterOptions() {
        uiterator = NULL;
    }

    StriUBreakIterator(const StriBrkIterOptions& bropt)
        : StriBrkIterOptions(bropt) {
        uiterator = NULL;
    }

    StriUBreakIterator& operator=(const StriBrkIterOptions& bropt) {
        this->~StriUBreakIterator();
        (StriBrkIterOptions&) (*this) = (StriBrkIterOptions&)bropt;
        uiterator = NULL;
        return *this;
    }

    ~StriUBreakIterator() {
        if (uiterator) {
            ubrk_close(uiterator);
            uiterator = NULL;
        }
    }

    void free(bool dealloc=true) {
        if (uiterator && dealloc) {
            ubrk_close(uiterator);
        }
        uiterator = NULL;
    }


    UBreakIterator* getIterator() {
        if (!uiterator) open();
        return uiterator;
    }


    const char* getLocale() {
        return locale;
    }
};


/**
 * A class to manage a break iterator
 *
 * @version 0.3-1 (Marek Gagolewski, 2014-10-30)
 *
 * @version 0.4-1 (Marek Gagolewski, 2014-12-02)
 * separate class
 *
 * @version 1.1.6 (Marek Gagolewski, 2017-04-22) Add support for RBBI
 *
 * @version 1.8.1 (Marek Gagolewski, 2023-11-09)
 *     warn if resource bundle for an explicitly set locale is unavailable
 */
class StriRuleBasedBreakIterator : public StriBrkIterOptions {
private:

    BreakIterator* rbiterator;
    UText* searchText;
    R_len_t searchPos; // may be BreakIterator::DONE
    const char* searchStr; // owned by caller
    R_len_t searchLen; // in bytes

    void setEmptyOpts() {
        rbiterator = NULL;
        searchText = NULL;
        searchPos = BreakIterator::DONE;
        searchStr = NULL;
        searchLen = 0;
    }

    void open() {
        UErrorCode status = U_ZERO_ERROR;
        Locale loc = Locale::createFromName(locale);
        if (!rules.isEmpty()) {
            UParseError parseErr;
            rbiterator = (BreakIterator*) new RuleBasedBreakIterator(
                             UnicodeString(rules), parseErr, status
                         );
        }
        else {
            switch (type) {
            case UBRK_CHARACTER: // character
                rbiterator = (BreakIterator*)BreakIterator::createCharacterInstance(loc, status);
                break;
            case UBRK_LINE: // line_break
                rbiterator = (BreakIterator*)BreakIterator::createLineInstance(loc, status);
                break;
            case UBRK_SENTENCE: // sentence
                rbiterator = (BreakIterator*)BreakIterator::createSentenceInstance(loc, status);
                break;
            case UBRK_WORD: // word
                rbiterator = (BreakIterator*)BreakIterator::createWordInstance(loc, status);
                break;
            default:
                throw StriException(MSG__INTERNAL_ERROR);
            }

        }
        STRI__CHECKICUSTATUS_THROW(status, {/* do nothing special on err */})

        if (status == U_USING_DEFAULT_WARNING && rbiterator && locale) {
            UErrorCode status2 = U_ZERO_ERROR;
            const char* valid_locale = rbiterator->getLocaleID(ULOC_VALID_LOCALE, status2);
            if (valid_locale && !strcmp(valid_locale, "root"))
                Rf_warning("%s", ICUError::getICUerrorName(status));
        }
    }

    bool ignoreBoundary();

public:

    StriRuleBasedBreakIterator()
        : StriBrkIterOptions() {
        setEmptyOpts();
    }

    StriRuleBasedBreakIterator(const StriBrkIterOptions& bropt)
        : StriBrkIterOptions(bropt) {
        setEmptyOpts();
    }

    StriRuleBasedBreakIterator& operator=(const StriBrkIterOptions& bropt) {
        this->~StriRuleBasedBreakIterator();
        (StriBrkIterOptions&) (*this) = (StriBrkIterOptions&)bropt;
        setEmptyOpts();
        return *this;
    }

    ~StriRuleBasedBreakIterator() {
        if (rbiterator) {
            delete rbiterator;
            rbiterator = NULL;
        }

        if (searchText) {
            utext_close(searchText);
            searchText = NULL;
        }
    }

    void setupMatcher(const char* searchStr, R_len_t searchLen);

    void first();
    bool next();
    bool next(std::pair<R_len_t, R_len_t>& bdr);

    void last();
    bool previous(std::pair<R_len_t, R_len_t>& bdr);
};

#endif
