/*
 * 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 <cstdint>
#include <string>
#include <utility>
#include <vector>

#include "paimon/result.h"
#include "paimon/status.h"

namespace paimon::benchmark {

using ParsedOptions = std::vector<std::pair<std::string, std::string>>;

inline bool ConsumeCliOption(const std::string& arg, const std::string& option_name,
                             std::string* value_out) {
    const std::string prefix = option_name + "=";
    if (arg.rfind(prefix, 0) != 0) {
        return false;
    }
    *value_out = arg.substr(prefix.size());
    return true;
}

inline std::string TrimAsciiWhitespace(const std::string& value) {
    const auto first = value.find_first_not_of(" \t\n\r");
    if (first == std::string::npos) {
        return "";
    }
    const auto last = value.find_last_not_of(" \t\n\r");
    return value.substr(first, last - first + 1);
}

inline Result<std::vector<std::string>> ParseCommaSeparatedColumns(const std::string& input,
                                                                   const std::string& option_name) {
    if (input.empty()) {
        return Status::Invalid("missing value for ", option_name);
    }

    std::vector<std::string> columns;
    size_t segment_start = 0;
    for (size_t index = 0; index <= input.size(); ++index) {
        if (index != input.size() && input[index] != ',') {
            continue;
        }

        const std::string column =
            TrimAsciiWhitespace(input.substr(segment_start, index - segment_start));
        if (column.empty()) {
            return Status::Invalid("invalid ", option_name, ": empty column name");
        }
        columns.push_back(column);
        segment_start = index + 1;
    }
    return columns;
}

inline Result<ParsedOptions> ParseDelimitedOptions(const std::string& input,
                                                   const std::string& option_name) {
    if (input.empty()) {
        return Status::Invalid("missing value for ", option_name);
    }

    ParsedOptions parsed;
    std::string token;
    for (size_t index = 0; index <= input.size(); ++index) {
        const bool at_end = (index == input.size());
        if (!at_end && input[index] != ';') {
            token.push_back(input[index]);
            continue;
        }

        const std::string segment = TrimAsciiWhitespace(token);
        if (segment.empty()) {
            return Status::Invalid("invalid ", option_name, ": empty option segment");
        }

        const auto separator = segment.find(':');
        if (separator == std::string::npos) {
            return Status::Invalid("invalid ", option_name, ": expected key:value");
        }

        const std::string key = TrimAsciiWhitespace(segment.substr(0, separator));
        const std::string value = TrimAsciiWhitespace(segment.substr(separator + 1));
        if (key.empty() || value.empty()) {
            return Status::Invalid("invalid ", option_name, ": expected key:value");
        }

        parsed.emplace_back(key, value);
        token.clear();
    }
    return parsed;
}

inline Result<bool> ParseStringOptionArg(int32_t argc, char** argv, const std::string& arg,
                                         const std::string& option_name, int32_t* arg_index,
                                         std::string* value_out) {
    std::string parsed_value;
    if (ConsumeCliOption(arg, option_name, &parsed_value)) {
        *value_out = std::move(parsed_value);
        return true;
    }

    if (arg != option_name) {
        return false;
    }

    if (*arg_index + 1 >= argc) {
        return Status::Invalid("missing value for ", option_name);
    }
    *value_out = argv[++(*arg_index)];
    return true;
}

inline Result<bool> ParseCommaSeparatedOptionArg(int32_t argc, char** argv, const std::string& arg,
                                                 const std::string& option_name, int32_t* arg_index,
                                                 std::vector<std::string>* columns_out) {
    std::string parsed_value;
    if (ConsumeCliOption(arg, option_name, &parsed_value)) {
        PAIMON_ASSIGN_OR_RAISE(*columns_out, ParseCommaSeparatedColumns(parsed_value, option_name));
        return true;
    }

    if (arg != option_name) {
        return false;
    }

    if (*arg_index + 1 >= argc) {
        return Status::Invalid("missing value for ", option_name);
    }
    PAIMON_ASSIGN_OR_RAISE(
        *columns_out, ParseCommaSeparatedColumns(std::string(argv[++(*arg_index)]), option_name));
    return true;
}

inline Result<bool> ParseDelimitedRepeatableOptionArg(
    int32_t argc, char** argv, const std::string& arg, const std::string& option_name,
    int32_t* arg_index, std::vector<std::pair<std::string, std::string>>* options_out) {
    std::string parsed_value;
    if (ConsumeCliOption(arg, option_name, &parsed_value)) {
        ParsedOptions parsed_options;
        PAIMON_ASSIGN_OR_RAISE(parsed_options, ParseDelimitedOptions(parsed_value, option_name));
        options_out->insert(options_out->end(), parsed_options.begin(), parsed_options.end());
        return true;
    }

    if (arg != option_name) {
        return false;
    }

    if (*arg_index + 1 >= argc) {
        return Status::Invalid("missing value for ", option_name);
    }

    const std::string option_arg = argv[++(*arg_index)];
    ParsedOptions parsed_options;
    PAIMON_ASSIGN_OR_RAISE(parsed_options, ParseDelimitedOptions(option_arg, option_name));
    options_out->insert(options_out->end(), parsed_options.begin(), parsed_options.end());
    return true;
}

}  // namespace paimon::benchmark
