#pragma once

#include "ascii.h"

#include <util/generic/string.h>
#include <util/generic/strbuf.h>
#include <utility>

template <class It>
struct TIsAsciiSpaceAdapter {
    bool operator()(const It& it) const noexcept {
        return IsAsciiSpace(*it);
    }
};

template <class It>
TIsAsciiSpaceAdapter<It> IsAsciiSpaceAdapter(It) {
    return {};
}

template <class TChar>
struct TEqualsStripAdapter {
    TEqualsStripAdapter(TChar ch)
        : Ch(ch)
    {
    }

    template <class It>
    bool operator()(const It& it) const noexcept {
        return *it == Ch;
    }

    const TChar Ch;
};

template <class TChar>
TEqualsStripAdapter<TChar> EqualsStripAdapter(TChar ch) {
    return {ch};
}

template <class It, class TStripCriterion>
inline void StripRangeBegin(It& b, const It& e, TStripCriterion&& criterion) noexcept {
    while (b < e && criterion(b)) {
        ++b;
    }
}

template <class It>
inline void StripRangeBegin(It& b, const It& e) noexcept {
    StripRangeBegin(b, e, IsAsciiSpaceAdapter(b));
}

template <class It, class TStripCriterion>
inline void StripRangeEnd(const It& b, It& e, TStripCriterion&& criterion) noexcept {
    while (b < e && criterion(e - 1)) {
        --e;
    }
}

template <class It>
inline void StripRangeEnd(const It& b, It& e) noexcept {
    StripRangeEnd(b, e, IsAsciiSpaceAdapter(b));
}

template <bool stripBeg, bool stripEnd>
struct TStripImpl {
    template <class It, class TStripCriterion>
    static inline bool StripRange(It& b, It& e, TStripCriterion&& criterion) noexcept {
        const size_t oldLen = e - b;

        if (stripBeg) {
            StripRangeBegin(b, e, criterion);
        }

        if (stripEnd) {
            StripRangeEnd(b, e, criterion);
        }

        const size_t newLen = e - b;
        return newLen != oldLen;
    }

    template <class T, class TStripCriterion>
    static inline bool StripString(const T& from, T& to, TStripCriterion&& criterion) {
        auto b = from.begin();
        auto e = from.end();

        if (StripRange(b, e, criterion)) {
            to = T(b, e - b);

            return true;
        }

        to = from;

        return false;
    }

    template <class T, class TStripCriterion>
    static inline T StripString(const T& from, TStripCriterion&& criterion) {
        T ret;
        StripString(from, ret, criterion);
        return ret;
    }

    template <class T>
    static inline T StripString(const T& from) {
        return StripString(from, IsAsciiSpaceAdapter(from.begin()));
    }
};

template <class It, class TStripCriterion>
inline bool StripRange(It& b, It& e, TStripCriterion&& criterion) noexcept {
    return TStripImpl<true, true>::StripRange(b, e, criterion);
}

template <class It>
inline bool StripRange(It& b, It& e) noexcept {
    return StripRange(b, e, IsAsciiSpaceAdapter(b));
}

template <class It, class TStripCriterion>
inline bool Strip(It& b, size_t& len, TStripCriterion&& criterion) noexcept {
    It e = b + len;

    if (StripRange(b, e, criterion)) {
        len = e - b;

        return true;
    }

    return false;
}

template <class It>
inline bool Strip(It& b, size_t& len) noexcept {
    return Strip(b, len, IsAsciiSpaceAdapter(b));
}

template <class T, class TStripCriterion>
static inline bool StripString(const T& from, T& to, TStripCriterion&& criterion) {
    return TStripImpl<true, true>::StripString(from, to, criterion);
}

template <class T>
static inline bool StripString(const T& from, T& to) {
    return StripString(from, to, IsAsciiSpaceAdapter(from.begin()));
}

template <class T, class TStripCriterion>
static inline T StripString(const T& from, TStripCriterion&& criterion) {
    return TStripImpl<true, true>::StripString(from, criterion);
}

template <class T>
static inline T StripString(const T& from) {
    return TStripImpl<true, true>::StripString(from);
}

template <class T>
static inline T StripStringLeft(const T& from) {
    return TStripImpl<true, false>::StripString(from);
}

template <class T>
static inline T StripStringRight(const T& from) {
    return TStripImpl<false, true>::StripString(from);
}

template <class T, class TStripCriterion>
static inline T StripStringLeft(const T& from, TStripCriterion&& criterion) {
    return TStripImpl<true, false>::StripString(from, criterion);
}

template <class T, class TStripCriterion>
static inline T StripStringRight(const T& from, TStripCriterion&& criterion) {
    return TStripImpl<false, true>::StripString(from, criterion);
}

/// Copies the given string removing leading and trailing spaces.
static inline bool Strip(const TString& from, TString& to) {
    return StripString(from, to);
}

/// Removes leading and trailing spaces from the string.
inline TString& StripInPlace(TString& s) {
    Strip(s, s);
    return s;
}

/// Returns a copy of the given string with removed leading and trailing spaces.
inline TString Strip(const TString& s) Y_WARN_UNUSED_RESULT;
inline TString Strip(const TString& s) {
    TString ret = s;
    Strip(ret, ret);
    return ret;
}

template <class TChar, class TWhitespaceFunc>
size_t CollapseImpl(TChar* s, size_t n, const TWhitespaceFunc& isWhitespace) {
    size_t newLen = 0;
    for (size_t i = 0; i < n; ++i, ++newLen) {
        size_t nextNonSpace = i;
        while (nextNonSpace < n && isWhitespace(s[nextNonSpace])) {
            ++nextNonSpace;
        }
        size_t numSpaces = nextNonSpace - i;
        if (numSpaces > 1 || (numSpaces == 1 && s[i] != ' ')) {
            s[newLen] = ' ';
            i = nextNonSpace - 1;
        } else {
            s[newLen] = s[i];
        }
    }
    return newLen;
}

template <class TStringType, class TWhitespaceFunc>
bool CollapseImpl(const TStringType& from, TStringType& to, size_t maxLen, const TWhitespaceFunc& isWhitespace) {
    to = from;
    maxLen = maxLen ? Min(maxLen, to.size()) : to.size();
    for (size_t i = 0; i < maxLen; ++i) {
        if (isWhitespace(to[i]) && (to[i] != ' ' || isWhitespace(to[i + 1]))) {
            size_t tailSize = maxLen - i;
            size_t newTailSize = CollapseImpl(to.begin() + i, tailSize, isWhitespace);
            to.remove(i + newTailSize, tailSize - newTailSize);
            return true;
        }
    }
    return false;
}

bool Collapse(const TString& from, TString& to, size_t maxLen = 0);

/// Replaces several consequtive space symbols with one (processing is limited to maxLen bytes)
inline TString& CollapseInPlace(TString& s, size_t maxLen = 0) {
    Collapse(s, s, maxLen);
    return s;
}

/// Replaces several consequtive space symbols with one (processing is limited to maxLen bytes)
inline TString Collapse(const TString& s, size_t maxLen = 0) Y_WARN_UNUSED_RESULT;
inline TString Collapse(const TString& s, size_t maxLen) {
    TString ret;
    Collapse(s, ret, maxLen);
    return ret;
}

void CollapseText(const TString& from, TString& to, size_t maxLen);

/// The same as Collapse() + truncates the string to maxLen.
/// @details An ellipsis is inserted at the end of the truncated line.
inline void CollapseText(TString& s, size_t maxLen) {
    TString to;
    CollapseText(s, to, maxLen);
    s = to;
}