/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include <stdlib.h>
#include <string>
#include <thread>

#include "fuzzy_path_wrapper.h"

#include "../vendor/MatcherBase.h"
#include "../vendor/score_match.h"

struct matcher {
  void *obj;
};

matcher_t *matcher_create()
{
  void *p;
  matcher_t *m;
  MatcherBase *obj;

  p = malloc(sizeof(*m));
  if (p == nullptr) {
    return nullptr;
  }

  obj = new MatcherBase();

  m = (matcher_t *)p;
  m->obj = obj;

  return m;
}

match_results_t matcher_find(matcher_t *m, const char *query, matcher_options_t options) {
  MatcherBase *obj;
  if (m == nullptr) {
  return {
      0,
      nullptr
    };
  }
  obj = static_cast<MatcherBase *>(m->obj);

  MatcherOptions cpp_options;
  cpp_options.first_match_can_be_weak = options.first_match_can_be_weak;
  cpp_options.num_threads = options.num_threads;
  cpp_options.max_results = options.max_results;
  cpp_options.weighted = options.weighted;

  std::string q(query);
  std::vector<MatchResult> matches = obj->findMatches(q, cpp_options);

  match_result_t *results = new match_result[matches.size()];
  for (size_t i = 0; i < matches.size(); i++) {
    results[i] = {
      matches[i].weight,
      matches[i].score,
      matches[i].value->c_str()
    };
  }

  return {
    matches.size(),
    results
  };
}

void matcher_add_candidate(matcher_t *m, const char *candidate, int weight) {
  MatcherBase *obj;
  if (m == nullptr) {
  return;
  }
  obj = static_cast<MatcherBase *>(m->obj);

  std::string s(candidate);
  obj->addCandidate(s, weight);
}

void matcher_add_candidates_bulk(matcher_t *m, const char **candidates, const int *weights, size_t count) {
  if (m == nullptr) return;
  MatcherBase *obj = static_cast<MatcherBase *>(m->obj);
  // Convert const char* to std::string
  std::vector<std::string> strs(count);
  for (size_t i = 0; i < count; i++) {
    strs[i] = candidates[i];
  }
  obj->addCandidatesBulk(strs.data(), weights, count);
}

void matcher_init_pair_bulk(
    matcher_t **out_a, const char **candidates_a, const int *weights_a, size_t count_a,
    matcher_t **out_b, const char **candidates_b, const int *weights_b, size_t count_b) {
  matcher_t *ma = matcher_create();
  matcher_t *mb = matcher_create();
  if (ma == nullptr || mb == nullptr) {
    *out_a = ma;
    *out_b = mb;
    return;
  }
  MatcherBase *obj_a = static_cast<MatcherBase *>(ma->obj);
  MatcherBase *obj_b = static_cast<MatcherBase *>(mb->obj);

  std::vector<std::string> strs_a(count_a);
  for (size_t i = 0; i < count_a; i++) {
    strs_a[i] = candidates_a[i];
  }
  std::vector<std::string> strs_b(count_b);
  for (size_t i = 0; i < count_b; i++) {
    strs_b[i] = candidates_b[i];
  }

  std::thread t([&]() {
    obj_b->addCandidatesBulk(strs_b.data(), weights_b, count_b);
  });
  obj_a->addCandidatesBulk(strs_a.data(), weights_a, count_a);
  t.join();

  *out_a = ma;
  *out_b = mb;
}

void matcher_remove_candidate(matcher_t *m, const char *candidate) {
  MatcherBase *obj;
  if (m == nullptr) {
  return;
  }
  obj = static_cast<MatcherBase *>(m->obj);

  std::string s(candidate);
  obj->removeCandidate(s);
}

void matcher_clear(matcher_t *m) {
  MatcherBase *obj;
  if (m == nullptr) {
  return;
  }
  obj = static_cast<MatcherBase *>(m->obj);
  obj->clear();
}

void matcher_reserve(matcher_t *m, size_t n) {
  MatcherBase *obj;
  if (m == nullptr) {
  return;
  }
  obj = static_cast<MatcherBase *>(m->obj);
  obj->reserve(n);
}

size_t matcher_size(matcher_t *m) {
  MatcherBase *obj;
  if (m == nullptr) {
  return 0;
  }
  obj = static_cast<MatcherBase *>(m->obj);
  return obj->size();
}

inline std::string str_to_lower(const std::string &s) {
  std::string lower(s);
  for (auto& c : lower) {
    if (c >= 'A' && c <= 'Z') {
      c += 'a' - 'A';
    }
  }
  return lower;
}

bool fuzzy_score_c(
    const char* haystack,
    const char* needle,
    bool boost_full_match,
    bool first_match_can_be_weak,
    int64_t* result) {
  std::string haystack_str(haystack);
  std::string haystack_lower = str_to_lower(haystack_str);

  std::string needle_str(needle);
  std::string needle_lower = str_to_lower(needle_str);

  MatchOptions options;
  options.boost_full_match = boost_full_match;
  options.first_match_can_be_weak = first_match_can_be_weak;

  return fuzzy_score(
      haystack,
      haystack_lower.c_str(),
      needle,
      needle_lower.c_str(),
      options,
      result);
}
