//          Copyright Joakim Karlsson & Kim Gräsman 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef SNOWHOUSE_ASSERT_H
#define SNOWHOUSE_ASSERT_H

#include "assertionexception.h"
#include "fluent/expressionbuilder.h"

// clang-format off
#define SNOWHOUSE_ASSERT_THAT(P1, P2, FAILURE_HANDLER) \
  ::snowhouse::ConfigurableAssert<FAILURE_HANDLER>::That((P1), (P2), __FILE__, __LINE__)

#ifndef SNOWHOUSE_NO_MACROS
# define AssertThat(P1, P2) \
  SNOWHOUSE_ASSERT_THAT((P1), (P2), ::snowhouse::DefaultFailureHandler)
#endif
// clang-format on

namespace snowhouse
{
  struct DefaultFailureHandler
  {
    template<typename ExpectedType, typename ActualType>
    static void Handle(const ExpectedType& expected, const ActualType& actual, const char* file_name, int line_number)
    {
      std::ostringstream str;

      str << "Expected: " << snowhouse::Stringize(expected) << std::endl;
      str << "Actual: " << snowhouse::Stringize(actual) << std::endl;

      throw AssertionException(str.str(), file_name, line_number);
    }

    static void Handle(const std::string& message)
    {
      throw AssertionException(message);
    }
  };

  template<typename FailureHandler>
  struct ConfigurableAssert
  {
    template<typename ActualType, typename ConstraintListType>
    static void That(const ActualType& actual, ExpressionBuilder<ConstraintListType> expression, const char* file_name = "", int line_number = 0)
    {
      try
      {
        ResultStack result;
        OperatorStack operators;
        expression.Evaluate(result, operators, actual);

        while (!operators.empty())
        {
          ConstraintOperator* op = operators.top();
          op->PerformOperation(result);
          operators.pop();
        }

        if (result.empty())
        {
          throw InvalidExpressionException("The expression did not yield any result");
        }

        if (!result.top())
        {
          FailureHandler::Handle(expression, actual, file_name, line_number);
        }
      }
      catch (const InvalidExpressionException& e)
      {
        FailureHandler::Handle("Malformed expression: \"" + snowhouse::Stringize(expression) + "\"\n" + e.what());
      }
    }

    template<typename ConstraintListType>
    static void That(const char* actual, ExpressionBuilder<ConstraintListType> expression, const char* file_name = "", int line_number = 0)
    {
      return That(std::string(actual), expression, file_name, line_number);
    }

    template<typename ActualType, typename ExpressionType>
    static void That(const ActualType& actual, const ExpressionType& expression, const char* file_name = "", int line_number = 0)
    {
      if (!expression(actual))
      {
        FailureHandler::Handle(expression, actual, file_name, line_number);
      }
    }

    template<typename ExpressionType>
    static void That(const char* actual, const ExpressionType& expression, const char* file_name = "", int line_number = 0)
    {
      return That(std::string(actual), expression, file_name, line_number);
    }

    static void That(bool actual)
    {
      if (!actual)
      {
        FailureHandler::Handle("Expected: true\nActual: false");
      }
    }

    static void Failure(const std::string& message)
    {
      FailureHandler::Handle(message);
    }
  };

  using Assert = ConfigurableAssert<DefaultFailureHandler>;
}

#endif
