/*
 * Copyright 2019 Google LLC
 *
 * Licensed 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
 *
 *     https://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 "cppbor.h"

namespace cppbor {

using ParseResult = std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
                               std::string /* errMsg */>;

/**
 * Parse the first CBOR data item (possibly compound) from the range [begin, end).
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 */
ParseResult parse(const uint8_t* begin, const uint8_t* end);

/**
 * Parse the first CBOR data item (possibly compound) from the range [begin, end).
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 *
 * The returned CBOR data item will contain View* items backed by
 * std::string_view types over the input range.
 * WARNING! If the input range changes underneath, the corresponding views will
 * carry the same change.
 */
ParseResult parseWithViews(const uint8_t* begin, const uint8_t* end);

/**
 * Parse the first CBOR data item (possibly compound) from the byte vector.
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 */
inline ParseResult parse(const std::vector<uint8_t>& encoding) {
    return parse(encoding.data(), encoding.data() + encoding.size());
}

/**
 * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size).
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 */
inline ParseResult parse(const uint8_t* begin, size_t size) {
    return parse(begin, begin + size);
}

/**
 * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size).
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 *
 * The returned CBOR data item will contain View* items backed by
 * std::string_view types over the input range.
 * WARNING! If the input range changes underneath, the corresponding views will
 * carry the same change.
 */
inline ParseResult parseWithViews(const uint8_t* begin, size_t size) {
    return parseWithViews(begin, begin + size);
}

/**
 * Parse the first CBOR data item (possibly compound) from the value contained in a Bstr.
 *
 * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
 * Item pointer is non-null, the buffer pointer points to the first byte after the
 * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
 * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
 * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
 * too large for the remaining buffer, etc.) and the string contains an error message describing the
 * problem encountered.
 */
inline ParseResult parse(const Bstr* bstr) {
    if (!bstr) return ParseResult(nullptr, nullptr, "Null Bstr pointer");
    return parse(bstr->value());
}

class ParseClient;

/**
 * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the
 * provided ParseClient when elements are found.
 */
void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient);

/**
 * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the
 * provided ParseClient when elements are found. Uses the View* item types
 * instead of the copying ones.
 */
void parseWithViews(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient);

/**
 * Parse the CBOR data in the vector in streaming fashion, calling methods on the
 * provided ParseClient when elements are found.
 */
inline void parse(const std::vector<uint8_t>& encoding, ParseClient* parseClient) {
    return parse(encoding.data(), encoding.data() + encoding.size(), parseClient);
}

/**
 * A pure interface that callers of the streaming parse functions must implement.
 */
class ParseClient {
  public:
    virtual ~ParseClient() {}

    /**
     * Called when an item is found.  The Item pointer points to the found item; use type() and
     * the appropriate as*() method to examine the value.  hdrBegin points to the first byte of the
     * header, valueBegin points to the first byte of the value and end points one past the end of
     * the item.  In the case of header-only items, such as integers, and compound items (ARRAY,
     * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to
     * the byte past the header.
     *
     * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content.  For
     * Map and Array items, the size() method will return a correct value, but the index operators
     * are unsafe, and the object cannot be safely compared with another Array/Map.
     *
     * The method returns a ParseClient*.  In most cases "return this;" will be the right answer,
     * but a different ParseClient may be returned, which the parser will begin using. If the method
     * returns nullptr, parsing will be aborted immediately.
     */
    virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
                              const uint8_t* valueBegin, const uint8_t* end) = 0;

    /**
     * Called when the end of a compound item (MAP or ARRAY) is found.  The item argument will be
     * the same one passed to the item() call -- and may be empty if item() moved its value out.
     * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the
     * first contained value, and one past the end of the last contained value, respectively.
     *
     * Note that the Item will have no content.
     *
     * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the
     * parsing by returning nullptr;
     */
    virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
                                 const uint8_t* valueBegin, const uint8_t* end) = 0;

    /**
     * Called when parsing encounters an error.  position is set to the first unparsed byte (one
     * past the last successfully-parsed byte) and errorMessage contains an message explaining what
     * sort of error occurred.
     */
    virtual void error(const uint8_t* position, const std::string& errorMessage) = 0;
};

}  // namespace cppbor
