// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <glog/logging.h>

#include <ostream>
#include <sstream>
#include <string>

#include "stout/abort.h"
#include "stout/error.h"
#include "stout/none.h"
#include "stout/option.h"
#include "stout/some.h"

////////////////////////////////////////////////////////////////////////

template <typename T>
class Result;

////////////////////////////////////////////////////////////////////////

template <typename T, typename E>
class Try;

////////////////////////////////////////////////////////////////////////

// A generic macro to facilitate definitions of CHECK_*, akin to CHECK.
// This appends the error if possible to the end of the log message,
// so there's no need to append the error message explicitly.
// To define a new CHECK_*, provide the name, the function that performs the
// check, and the expression. See below for examples (e.g. CHECK_SOME).
#define CHECK_STATE(name, check, expression)                             \
  for (const Option<Error> _error = check(expression); _error.isSome();) \
  _CheckFatal(__FILE__, __LINE__, #name, #expression, _error.get()).stream()

////////////////////////////////////////////////////////////////////////

#define CHECK_SOME(expression) \
  CHECK_STATE(CHECK_SOME, _check_some, expression)

////////////////////////////////////////////////////////////////////////

#define CHECK_NONE(expression) \
  CHECK_STATE(CHECK_NONE, _check_none, expression)

////////////////////////////////////////////////////////////////////////

#define CHECK_ERROR(expression) \
  CHECK_STATE(CHECK_ERROR, _check_error, expression)

////////////////////////////////////////////////////////////////////////

// A private helper for CHECK_NOTNONE which is similar to the
// CHECK_NOTNULL provided by glog.
template <typename T>
T&& _check_not_none(
    const char* file,
    int line,
    const char* message,
    Option<T>&& t) {
  if (t.isNone()) {
    google::LogMessageFatal(file, line, new std::string(message));
  }
  return std::move(t).get();
}

////////////////////////////////////////////////////////////////////////

template <typename T>
T& _check_not_none(
    const char* file,
    int line,
    const char* message,
    Option<T>& t) {
  if (t.isNone()) {
    google::LogMessageFatal(file, line, new std::string(message));
  }
  return t.get();
}

////////////////////////////////////////////////////////////////////////

template <typename T>
const T& _check_not_none(
    const char* file,
    int line,
    const char* message,
    const Option<T>& t) {
  if (t.isNone()) {
    google::LogMessageFatal(file, line, new std::string(message));
  }
  return t.get();
}

////////////////////////////////////////////////////////////////////////
#define CHECK_NOTNONE(expression)       \
  _check_not_none(                      \
      __FILE__,                         \
      __LINE__,                         \
      "'" #expression "' Must be SOME", \
      (expression))

////////////////////////////////////////////////////////////////////////

// A private helper for CHECK_NOTERROR which is similar to the
// CHECK_NOTNULL provided by glog.
template <typename T, typename E>
T&& _check_not_error(
    const char* file,
    int line,
    const char* message,
    Try<T, E>&& t) {
  if (t.isError()) {
    google::LogMessageFatal(
        file,
        line,
        new std::string(
            std::string(message) + ": " + Error(t.error()).message));
  }
  return std::move(t).get();
}

////////////////////////////////////////////////////////////////////////

template <typename T, typename E>
T& _check_not_error(
    const char* file,
    int line,
    const char* message,
    Try<T, E>& t) {
  if (t.isError()) {
    google::LogMessageFatal(
        file,
        line,
        new std::string(
            std::string(message) + ": " + Error(t.error()).message));
  }
  return t.get();
}

////////////////////////////////////////////////////////////////////////

template <typename T, typename E>
const T& _check_not_error(
    const char* file,
    int line,
    const char* message,
    const Try<T, E>& t) {
  if (t.isError()) {
    google::LogMessageFatal(
        file,
        line,
        new std::string(
            std::string(message) + ": " + Error(t.error()).message));
  }
  return t.get();
}

////////////////////////////////////////////////////////////////////////

#define CHECK_NOTERROR(expression)      \
  _check_not_error(                     \
      __FILE__,                         \
      __LINE__,                         \
      "'" #expression "' Must be SOME", \
      (expression))

////////////////////////////////////////////////////////////////////////

// Private structs/functions used for CHECK_*.

template <typename T>
Option<Error> _check_some(const Option<T>& o) {
  if (o.isNone()) {
    return Error("is NONE");
  } else {
    CHECK(o.isSome());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T, typename E>
Option<Error> _check_some(const Try<T, E>& t) {
  if (t.isError()) {
    return Error(t.error());
  } else {
    CHECK(t.isSome());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T>
Option<Error> _check_some(const Result<T>& r) {
  if (r.isError()) {
    return Error(r.error());
  } else if (r.isNone()) {
    return Error("is NONE");
  } else {
    CHECK(r.isSome());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T>
Option<Error> _check_none(const Option<T>& o) {
  if (o.isSome()) {
    return Error("is SOME");
  } else {
    CHECK(o.isNone());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T>
Option<Error> _check_none(const Result<T>& r) {
  if (r.isError()) {
    return Error("is ERROR");
  } else if (r.isSome()) {
    return Error("is SOME");
  } else {
    CHECK(r.isNone());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T, typename E>
Option<Error> _check_error(const Try<T, E>& t) {
  if (t.isSome()) {
    return Error("is SOME");
  } else {
    CHECK(t.isError());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

template <typename T>
Option<Error> _check_error(const Result<T>& r) {
  if (r.isNone()) {
    return Error("is NONE");
  } else if (r.isSome()) {
    return Error("is SOME");
  } else {
    CHECK(r.isError());
    return None();
  }
}

////////////////////////////////////////////////////////////////////////

struct _CheckFatal {
  _CheckFatal(
      const char* _file,
      int _line,
      const char* type,
      const char* expression,
      const Error& error)
    : file(_file),
      line(_line) {
    out << type << "(" << expression << "): " << error.message << " ";
  }

  ~_CheckFatal() {
    google::LogMessageFatal(file.c_str(), line).stream() << out.str();
  }

  std::ostream& stream() {
    return out;
  }

  const std::string file;
  const int line;
  std::ostringstream out;
};

////////////////////////////////////////////////////////////////////////

// Check on whether some container (that supports the contains()
// member function) contains the given key. This prints a message
// of the form:
//
// Check failed: $ContainerName does not contain $Value
#define CHECK_CONTAINS(container, key)                 \
  if (!(container).contains(key))                      \
  google::LogMessageFatal(__FILE__, __LINE__).stream() \
      << "Check failed: " << #container << " does not contain " << (key)

////////////////////////////////////////////////////////////////////////

// Check on whether some container (that supports the contains()
// member function) does not contain the given key. This prints
// a message of the form:
//
// Check failed: $ContainerName already contains $Value
#define CHECK_NOT_CONTAINS(container, key)             \
  if ((container).contains(key))                       \
  google::LogMessageFatal(__FILE__, __LINE__).stream() \
      << "Check failed: " << #container << " already contains " << (key)

////////////////////////////////////////////////////////////////////////
