/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#pragma once

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "paimon/common/data/binary_section.h"
#include "paimon/memory/memory_segment.h"
#include "paimon/visibility.h"

namespace paimon {
class Bytes;
class MemoryPool;

/// A string which is backed by a single `MemorySegment`.
///
/// @note: Unlike the Java implementation where a BinaryString may span multiple
/// MemorySegments, in this C++ implementation all data resides within a single MemorySegment.
class PAIMON_EXPORT BinaryString : public BinarySection {
 public:
    static const BinaryString& EmptyUtf8();

    BinaryString(const MemorySegment& segment, int32_t offset, int32_t size_in_bytes);

    static BinaryString FromAddress(const MemorySegment& segment, int32_t offset,
                                    int32_t num_bytes);
    static BinaryString FromString(const std::string& str, MemoryPool* pool);

    /// Creates a `BinaryString` instance from the given UTF-8 bytes.
    static BinaryString FromBytes(const std::shared_ptr<Bytes>& bytes);
    /// Creates a `BinaryString` instance from the given UTF-8 bytes with offset and number of
    /// bytes.
    static BinaryString FromBytes(const std::shared_ptr<Bytes>& bytes, int32_t offset,
                                  int32_t num_bytes);
    /// Creates a `BinaryString` instance that contains length spaces.
    static BinaryString BlankString(int32_t length, MemoryPool* pool);

    std::string ToString() const;

    bool operator==(const BinaryString& other) const {
        if (this == &other) {
            return true;
        }
        return CompareTo(other) == 0;
    }
    bool operator<(const BinaryString& other) const {
        return CompareTo(other) < 0;
    }

    int32_t CompareTo(const BinaryString& other) const;

    /// @return the number of UTF-8 code points in the string.
    int32_t NumChars() const;

    /// Returns the byte value at the specified index. An index ranges from `0` to
    /// `size_in_bytes_ - 1`.
    /// @param index the index of the byte value.
    /// @return the byte value at the specified index of this UTF-8 bytes.
    char ByteAt(int32_t index) const;

    /// Copy a new BinaryString.
    BinaryString Copy(MemoryPool* pool) const;

    /// Returns a binary string that is a substring of this binary string. The substring
    /// begins at the specified `begin_index` and extends to the character at index
    /// `end_index - 1`.
    /// Examples:
    /// FromString("hamburger").Substring(4, 8) returns binary string "urge"
    /// FromString("smiles").Substring(1, 5) returns binary string "mile"
    ///
    /// @param begin_index the beginning index, inclusive.
    /// @param end_index the ending index, exclusive.
    /// @return the specified substring, return `EmptyUtf8()` when index out of bounds
    BinaryString Substring(int32_t begin_index, int32_t end_index, MemoryPool* pool) const;

    bool Contains(const BinaryString& s) const;

    /// Tests if this BinaryString starts with the specified prefix.
    /// @param prefix the prefix.
    /// @return `true` if the bytes represented by the argument is a prefix of the
    /// bytes represented by this string; `false` otherwise. Note also that `true` will be returned
    /// if the argument is an empty BinaryString or is equal to this BinaryString object as
    /// determined by the `operator==` method.
    bool StartsWith(const BinaryString& prefix) const;

    /// Tests if this BinaryString ends with the specified suffix.
    /// @param suffix the suffix.
    /// @return `true` if the bytes represented by the argument is a suffix of the
    /// bytes represented by this object; `false` otherwise. Note that the result
    /// will be `true` if the argument is the empty string or is equal to this BinaryString object
    /// as determined by the `operator==` method.
    bool EndsWith(const BinaryString& suffix) const;

    /// Returns the index within this string of the first occurrence of the specified
    /// substring, starting at the specified index.
    /// @param str the substring to search for.
    /// @param from_index the index from which to start the search.
    /// @return the utf8 index of the first occurrence of the specified substring, starting
    /// at the specified index, or `-1` if there is no such occurrence.
    int32_t IndexOf(const BinaryString& str, int32_t from_index) const;

    /// Converts all of the characters in this BinaryString to upper case.
    /// @return the BinaryString, converted to uppercase.
    BinaryString ToUpperCase(MemoryPool* pool) const;

    /// Converts all of the characters in this BinaryString to lower case.
    /// @return the BinaryString, converted to lowercase.
    BinaryString ToLowerCase(MemoryPool* pool) const;

    std::string_view GetStringView() const;

    // @return copied sub string from [start, end].
    BinaryString CopyBinaryString(int32_t start, int32_t end, MemoryPool* pool) const;

    /// @return the number of bytes for a code point with the first byte as b.
    /// @param b The first byte of a code point
    static int32_t NumBytesForFirstByte(char b);

 private:
    char GetByteOneSegment(int32_t i) const;
    bool MatchAt(const BinaryString& s, int32_t pos) const;
    bool MatchAtOneSeg(const BinaryString& s, int32_t pos) const;

    /// CurrentSegment and positionInSegment.
    class SegmentAndOffset {
        friend class BinaryString;

     public:
        SegmentAndOffset(const MemorySegment& segment, int32_t offset)
            : offset_(offset), segment_(segment) {}

        void NextByte(int32_t seg_size) {
            offset_++;
        }

        void SkipBytes(int32_t n, int32_t seg_size) {
            offset_ += n;
        }

        char Value() const {
            return segment_.Get(offset_);
        }

     private:
        int32_t offset_;
        MemorySegment segment_;
    };
    SegmentAndOffset StartSegmentAndOffset(int32_t seg_size) const;

    BinaryString();

    BinaryString CppToLowerCase(MemoryPool* pool) const;
    BinaryString CppToUpperCase(MemoryPool* pool) const;
};

}  // namespace paimon
