//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2019 James D. Mitchell
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include "libsemigroups/report.hpp"

#include <iostream>

#include "libsemigroups/config.hpp"

namespace libsemigroups {
  detail::Reporter        REPORTER;
  detail::ThreadIdManager THREAD_ID_MANAGER;

  namespace detail {

    ThreadIdManager::ThreadIdManager() : _mtx(), _next_tid(0), _thread_map() {
      tid(std::this_thread::get_id());
    }

    void ThreadIdManager::reset() {
      // Only do this from the main thread
      LIBSEMIGROUPS_ASSERT(tid(std::this_thread::get_id()) == 0);
      // Delete all thread_ids
      _thread_map.clear();
      _next_tid = 0;
      // Reinsert the main thread's id
      tid(std::this_thread::get_id());
    }

    size_t ThreadIdManager::tid(std::thread::id t) {
      std::lock_guard<std::mutex> lg(_mtx);
      auto                        it = _thread_map.find(t);
      if (it != _thread_map.end()) {
        return (*it).second;
      } else {
        // Don't check the assert below because on a single thread machine
        // (such as those used by appveyor), for an fp-semigroup more than 1
        // thread will be used, and this assertion will fail.
        // LIBSEMIGROUPS_ASSERT(_next_tid <=
        // std::thread::hardware_concurrency());
        _thread_map.emplace(t, _next_tid++);
        return _next_tid - 1;
      }
    }

    Reporter::Reporter(bool report)
        : _last_msg(), _mtx(), _msg(), _options(), _report(report) {}

#ifdef LIBSEMIGROUPS_FMT_ENABLED
    Reporter& Reporter::color(fmt::color c) {
      if (_report) {
        size_t tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
        resize(tid + 1);
        _options[tid].color = c;
      }
      return *this;
    }

    Reporter& Reporter::thread_color() {
      if (_report) {
        std::lock_guard<std::mutex> lg(_mtx);
        size_t tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
        resize(tid + 1);
        _options[tid].color = thread_colors[tid % thread_colors.size()];
      }
      return *this;
    }
#endif

    Reporter& Reporter::prefix() {
      if (_report) {
        std::lock_guard<std::mutex> lg(_mtx);
        size_t tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
        resize(tid + 1);
        _options[tid].prefix = "";
      }
      return *this;
    }

    Reporter& Reporter::flush_right() {
      if (_report) {
        std::lock_guard<std::mutex> lg(_mtx);
        size_t tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
        resize(tid + 1);
        _options[tid].flush_right = true;
      }
      return *this;
    }

    void Reporter::flush() {
      if (_report) {
        std::lock_guard<std::mutex> lg(_mtx);
        size_t      tid  = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
        auto const& prfx = _options[tid].prefix;
        auto        pos  = prfx.find(":");
        if (pos != std::string::npos && pos + 2 <= prfx.size() - 1) {
          std::string class_name(prfx.cbegin() + prfx.find(":") + 2,
                                 prfx.cend() - 2);
          if (_suppressions.find(class_name) != _suppressions.cend()) {
            return;
          }
        }

        size_t pad = 0;
        _msg[tid]  = _options[tid].prefix + _msg[tid];
        if (_options[tid].flush_right
            && _last_msg[tid].size() + unicode_string_length(_msg[tid]) < 80) {
          pad = (80 - _last_msg[tid].size()) - unicode_string_length(_msg[tid]);
          _msg[tid] = std::string(pad, ' ') + _msg[tid];
        }
#ifdef LIBSEMIGROUPS_VERBOSE
        if (_msg[tid].back() != '\n') {
          _msg[tid] += "\n";
        }
#endif
        _msg[tid] = wrap(_options[tid].prefix.length(), _msg[tid]);
#ifdef LIBSEMIGROUPS_FMT_ENABLED
        fmt::print(fg(_options[tid].color), _msg[tid]);
#else
        std::cout << _msg[tid];
#endif
        _options[tid] = Options();
      }
    }

    void Reporter::resize(size_t n) {
      if (n > _msg.size()) {
        _last_msg.resize(n);
        _msg.resize(n);
        _options.resize(n);
      }
    }

    PrintTable::~PrintTable() = default;
  }  // namespace detail

  namespace report {
    bool should_report() noexcept {
      return REPORTER.report();
    }

    void suppress(std::string const& class_name) {
      REPORTER.suppress(class_name);
    }

    void clear_suppressions() {
      REPORTER.clear_suppressions();
    }

  }  // namespace report
}  // namespace libsemigroups
