#pragma once

#include <array>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <string_view>
#include <vector>

namespace lymphosat {

inline constexpr std::string_view kFormulaProfileSchemaVersion = "formula-profile-v1";
inline constexpr std::string_view kProfilerVersion = "0.1.0";

enum ClauseLengthBucket : std::size_t {
    kLen0 = 0,
    kLen1,
    kLen2,
    kLen3,
    kLen4,
    kLen5,
    kLen6,
    kLen7,
    kLen8,
    kLen9To16,
    kLen17To32,
    kLen33To64,
    kLen65Plus,
    kClauseLengthBucketCount,
};

inline constexpr std::array<std::string_view, kClauseLengthBucketCount>
    kClauseLengthBucketNames = {
        "clause_len_0",
        "clause_len_1",
        "clause_len_2",
        "clause_len_3",
        "clause_len_4",
        "clause_len_5",
        "clause_len_6",
        "clause_len_7",
        "clause_len_8",
        "clause_len_9_16",
        "clause_len_17_32",
        "clause_len_33_64",
        "clause_len_65_plus",
};

struct FormulaProfile {
    std::string schema_version = std::string(kFormulaProfileSchemaVersion);
    std::string profiler_version = std::string(kProfilerVersion);
    std::string source_path;
    std::string parse_error_message;

    bool truncated = false;
    bool parse_error = false;
    bool empty_clause_present = false;
    bool conflicting_units_present = false;
    bool binary_pair_sketch_truncated = false;

    std::uint64_t profile_clauses_limit = 0;
    double parse_seconds = 0.0;

    std::uint64_t num_vars = 0;
    std::uint64_t header_clauses = 0;
    std::uint64_t clauses_seen = 0;
    std::uint64_t num_literals = 0;
    std::uint64_t max_var_seen = 0;
    std::uint64_t used_var_count = 0;
    std::uint64_t unused_var_count = 0;

    std::uint64_t max_clause_len = 0;
    std::uint64_t min_clause_len = 0;
    double avg_clause_len = 0.0;
    double clauses_per_var = 0.0;
    double literals_per_var = 0.0;
    double used_var_fraction = 0.0;

    std::array<std::uint64_t, kClauseLengthBucketCount> clause_len_counts{};
    std::array<double, kClauseLengthBucketCount> clause_len_fracs{};
    std::uint64_t distinct_clause_len_count = 0;
    std::uint64_t dominant_clause_len = 0;
    double dominant_clause_len_fraction = 0.0;

    std::uint64_t positive_literals = 0;
    std::uint64_t negative_literals = 0;
    double positive_literal_fraction = 0.0;
    double negative_literal_fraction = 0.0;
    double mean_positive_literals_per_clause = 0.0;
    double mean_negative_literals_per_clause = 0.0;

    std::uint64_t all_positive_clauses = 0;
    std::uint64_t all_negative_clauses = 0;
    std::uint64_t mixed_clauses = 0;
    double all_positive_clause_fraction = 0.0;
    double all_negative_clause_fraction = 0.0;
    double mixed_clause_fraction = 0.0;

    std::uint64_t horn_clauses = 0;
    std::uint64_t dual_horn_clauses = 0;
    std::uint64_t definite_horn_clauses = 0;
    double horn_clause_fraction = 0.0;
    double dual_horn_clause_fraction = 0.0;
    double definite_horn_clause_fraction = 0.0;

    std::uint64_t unit_clauses = 0;
    std::uint64_t binary_clauses = 0;
    std::uint64_t ternary_clauses = 0;
    double unit_clause_fraction = 0.0;
    double binary_clause_fraction = 0.0;
    double ternary_clause_fraction = 0.0;

    std::uint64_t negative_binary_clauses = 0;
    std::uint64_t positive_binary_clauses = 0;
    std::uint64_t mixed_binary_clauses = 0;
    double negative_binary_fraction = 0.0;
    double positive_binary_fraction = 0.0;
    double mixed_binary_fraction = 0.0;
    std::uint64_t unique_binary_pairs_sampled = 0;
    double binary_duplicate_estimate = 0.0;
    std::uint64_t binary_vars_touched = 0;
    double binary_vars_touched_fraction = 0.0;
    double binary_graph_degree_mean = 0.0;
    std::uint64_t binary_graph_degree_max = 0;

    std::uint64_t tautological_clauses = 0;
    std::uint64_t repeated_literal_clauses = 0;
    double tautological_clause_fraction = 0.0;
    double repeated_literal_clause_fraction = 0.0;

    double occurrence_mean = 0.0;
    double occurrence_stddev = 0.0;
    std::uint64_t occurrence_max = 0;
    std::uint64_t singleton_vars = 0;
    std::uint64_t pure_vars = 0;
    std::uint64_t positive_only_vars = 0;
    std::uint64_t negative_only_vars = 0;
    double singleton_var_fraction = 0.0;
    double pure_var_fraction = 0.0;
    double positive_only_var_fraction = 0.0;
    double negative_only_var_fraction = 0.0;
    double polarity_balance_mean = 0.0;
    double top1_occurrence_mass = 0.0;
    double top5_occurrence_mass = 0.0;

    double clause_span_mean = 0.0;
    double normalized_clause_span_mean = 0.0;
    double small_span_clause_fraction = 0.0;

    std::uint64_t positive_block_candidate_len = 0;
    std::uint64_t positive_block_candidate_count = 0;
    double positive_block_clause_fraction = 0.0;
    double exactly_one_intra_pair_coverage = 0.0;
};

struct ProfileOptions {
    std::uint64_t max_clauses = 1'000'000;
    std::uint64_t max_binary_pairs = 2'000'000;
    std::uint64_t max_positive_block_samples_per_len = 10'000;
};

struct FeatureValue {
    std::string_view name;
    double value;
};

using MatchFunction = bool (*)(const FormulaProfile& profile);

struct MatcherEntry {
    const char* name;
    const char* solver_path;
    MatchFunction match;
};

FormulaProfile profile_formula(const std::string& path, const ProfileOptions& options = {});
std::string profile_to_json(const FormulaProfile& profile);
FormulaProfile profile_from_json(const std::string& json);
std::vector<FeatureValue> profile_features(const FormulaProfile& profile);
double profile_feature(const FormulaProfile& profile, std::string_view name);

const std::vector<MatcherEntry>& matcher_registry();
const char* fallback_solver_name();

}  // namespace lymphosat

#define LYMPHOSAT_MATCHER_CONCAT_INNER(a, b) a##b
#define LYMPHOSAT_MATCHER_CONCAT(a, b) LYMPHOSAT_MATCHER_CONCAT_INNER(a, b)
#define LYMPHOSAT_MATCHER_FN(symbol) LYMPHOSAT_MATCHER_CONCAT(lymphosat_match_, symbol)

#ifndef LYMPHOSAT_MATCHER_SYMBOL
#define LYMPHOSAT_MATCHER_SYMBOL standalone
#endif

#define LYMPHOSAT_MATCHER \
    extern "C" bool LYMPHOSAT_MATCHER_FN(LYMPHOSAT_MATCHER_SYMBOL)( \
        const lymphosat::FormulaProfile& profile)
