text: /*  Copyright (c) 2022 Mitya Selivanov
   *
   *  This file is part of the Puzzle project.
   *
   *  Laplace is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty
   *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
   *  the MIT License for more details.
   */
  
  #include "puzzle.h"
  #include <nlohmann/json.hpp>
  
  namespace puzzle {
    auto run_tests() -> int;
  }
  
  auto is_flag(std::span<char *> args, std::string_view flag) {
    return std::any_of(args.begin(), args.end(),
                       [&](auto arg) { return arg && flag == arg; });
  }
  
  auto convert_state(nlohmann::json const &data, bool &ok)
      -> puzzle::grid_state {
    using std::cerr, nlohmann::json, std::vector;
  
    auto state = puzzle::grid_state {};
  
    if (data.contains("width"))
      state.width = data["width"];
    if (data.contains("height"))
      state.height = data["height"];
    if (data.contains("region_count"))
      state.region_count = data["region_count"];
    if (data.contains("value_count"))
      state.value_count = data["value_count"];
  
    if (state.width <= 0) {
      cerr << "Invalid width.\n";
      ok = false;
    }
  
    if (state.height <= 0) {
      cerr << "Invalid height.\n";
      ok = false;
    }
  
    if (state.region_count <= 0) {
      cerr << "Invalid region count.\n";
      ok = false;
    }
  
    if (state.value_count <= 0) {
      cerr << "Invalid value count.\n";
      ok = false;
    }
  
    if (state.width * state.height > puzzle::grid_state::max_size) {
      cerr << "Too big grid size.\n";
      ok = false;
    }
  
    if (data.contains("regions")) {
      auto regions = data["regions"].get<vector<json>>();
      if (regions.size() != state.width * state.height) {
        cerr << "Invalid regions array size.\n";
        ok = false;
      } else {
        for (int i = 0; i < state.width * state.height; i++)
          state.cells[i].region = regions[i];
      }
    } else if (state.width == 9 && state.height == 9) {
      for (int i = 0; i < state.width * state.height; i++)
        state.cells[i].region = puzzle::get_sudoku_region(i);
    }
  
    if (data.contains("values")) {
      auto values = data["values"].get<vector<json>>();
      if (values.size() != state.width * state.height) {
        cerr << "Invalid values array size.\n";
        ok = false;
      } else {
        for (int i = 0; i < state.width * state.height; i++)
          state.cells[i].value = values[i];
      }
    }
  
    return state;
  }
  auto convert_rules(nlohmann::json const &data, bool &ok)
      -> puzzle::rule_check {
    return puzzle::get_sudoku_rules();
  }
  
  auto convert_depth(nlohmann::json const &data, bool &ok) -> int {
    using std::cerr;
  
    int depth = 100;
  
    if (data.contains("depth")) {
      depth = data["depth"];
      if (depth < 0) {
        cerr << "Invalid depth.\n";
        ok = false;
      }
    }
  
    return depth;
  }
  
  void print_solution(puzzle::solution solution) {
    using std::cout;
  
    if (!solution.is_complete)
      cout << "Solution is incomplete.\n";
  
    if (solution.states.empty())
      cout << "No solution.\n";
    else if (solution.states.size() > 1)
      cout << "Multiple solutions.\n";
  
    cout << "Solution depth: " << solution.depth << '\n';
  
    if (!solution.states.empty()) {
      cout << '\n';
      puzzle::print_digits(cout, solution.states[0]);
    }
  }
  
  auto process_json() -> int {
    using nlohmann::json, std::cin, std::cout, std::cerr, std::vector;
  
    try {
      auto data = json {};
      cin >> data;
  
      bool ok = true;
  
      auto state = convert_state(data, ok);
      auto rules = convert_rules(data, ok);
      int  depth = convert_depth(data, ok);
  
      if (!ok)
        return 1;
  
      print_solution(puzzle::solve(rules, state,
                                   puzzle::mode::find_single, depth));
  
    } catch (json::type_error &error) {
      cerr << error.what();
      return 1;
    } catch (json::parse_error &error) {
      cerr << error.what();
      return 1;
    } catch (json::other_error &error) {
      cerr << error.what();
      return 1;
    } catch (...) { return 1; }
  
    return 0;
  }
  
  auto main(int argc, char **argv) -> int {
    using std::string, std::cin, std::cout, nlohmann::json;
  
    bool is_tests = is_flag({ argv, argv + argc }, "--tests");
    bool is_json  = is_flag({ argv, argv + argc }, "--json");
  
    if (is_tests) {
      auto status = puzzle::run_tests();
  
      if (status != 0 || !is_json)
        return status;
    }
  
    if (is_json)
      return process_json();
  
    return 0;
  }
  