/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2020 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/base/filesystem_wildcard.h"

#include <algorithm>

#include "xenia/base/assert.h"
#include "xenia/base/string.h"

namespace xe::filesystem {

WildcardFlags WildcardFlags::FIRST(true, false, false);
WildcardFlags WildcardFlags::LAST(false, true, false);
WildcardFlags WildcardFlags::ANY(false, false, true);

WildcardFlags::WildcardFlags()
    : FromStart(false), ToEnd(false), ExactLength(false) {}

WildcardFlags::WildcardFlags(bool start, bool end, bool exact_length)
    : FromStart(start), ToEnd(end), ExactLength(exact_length) {}

WildcardRule::WildcardRule(const std::string_view match,
                           const WildcardFlags& flags)
    : match_(utf8::lower_ascii(match)), rules_(flags) {}

bool WildcardRule::Check(const std::string_view lower,
                         std::string::size_type* offset) const {
  if (match_.empty()) {
    return true;
  }

  if ((lower.size() - *offset) < match_.size()) {
    return false;
  }

  if (rules_.ExactLength) {
    *offset += match_.size();
    return true;
  }

  std::string_view::size_type result(lower.find(match_, *offset));

  if (result != std::string_view::npos) {
    if (rules_.FromStart && result != *offset) {
      return false;
    }

    if (rules_.ToEnd && result != (lower.size() - match_.size())) {
      return false;
    }

    *offset = (result + match_.size());
    return true;
  }

  return false;
}

void WildcardEngine::PreparePattern(const std::string_view pattern) {
  rules_.clear();

  WildcardFlags flags(WildcardFlags::FIRST);
  size_t n = 0;
  size_t last = 0;
  while ((n = pattern.find_first_of("*?", last)) != pattern.npos) {
    if (last != n) {
      std::string str_str(pattern.substr(last, n - last));
      rules_.push_back(WildcardRule(str_str, flags));
    }
    if (pattern[n] == '?') {
      auto end = pattern.find_first_not_of('?', n + 1);
      auto count = end == pattern.npos ? (pattern.size() - n) : (end - n);
      rules_.push_back(
          WildcardRule(pattern.substr(n, count), WildcardFlags::ANY));
      last = n + count;
    } else if (pattern[n] == '*') {
      last = n + 1;
    } else {
      assert_always();
    }
    flags = WildcardFlags();
  }
  if (last != pattern.size()) {
    std::string str_str(pattern.substr(last));
    rules_.push_back(WildcardRule(str_str, WildcardFlags::LAST));
  }
}

void WildcardEngine::SetRule(const std::string_view pattern) {
  PreparePattern(pattern);
}

bool WildcardEngine::Match(const std::string_view str) const {
  std::string str_lc = utf8::lower_ascii(str);
  std::string::size_type offset(0);
  for (const auto& rule : rules_) {
    if (!(rule.Check(str_lc, &offset))) {
      return false;
    }
  }

  return true;
}

}  // namespace xe::filesystem
