#include "stdafx.h"

#include "xtr/core/PyCore.h"

#pragma warning( push )
#pragma warning( disable : 4100 )
#include "Python.h"
#pragma warning( pop )

using cxxpython::PyCore;
using cxxpython::Object;

class UTPyCore : public ::testing::Test {
protected:
  inline static const std::string sampleModule = "sample_module";
  inline static const std::string moduleKey = "sample";

  PyCore& python{ PyCore::Instance() };

  void SetUp() override {
    python.Start();
  }

  void TearDown() override {
    python.Stop();
  }
};

TEST_F(UTPyCore, Start) {
  python.Stop();
  EXPECT_FALSE(python.IsRunning());
  ASSERT_TRUE(python.Start());
  EXPECT_FALSE(python.Start());
  EXPECT_TRUE(python.IsRunning());
  python.Stop();
}

TEST_F(UTPyCore, Stop) {
  python.Stop();
  EXPECT_FALSE(python.IsRunning());

  EXPECT_NO_THROW(python.Stop());
}

TEST_F(UTPyCore, ObjectNullptr) {
  EXPECT_TRUE(Object(nullptr) == nullptr);
  EXPECT_TRUE(Object::FromNew(nullptr) == nullptr);
  EXPECT_TRUE(Object::FromBorrowed(nullptr) == nullptr);
}

TEST_F(UTPyCore, ObjectHandles) {
  auto newTuple = Object::FromNew(PyTuple_New(1));
  ASSERT_TRUE(newTuple != nullptr);
  EXPECT_EQ(Py_REFCNT(newTuple.get()), 1);

  const auto borrowed = Object::FromBorrowed(newTuple.get());
  EXPECT_EQ(Py_REFCNT(borrowed.get()), 2);

  newTuple = nullptr;
  EXPECT_EQ(Py_REFCNT(borrowed.get()), 1);

  auto copy = borrowed;
  EXPECT_EQ(Py_REFCNT(copy.get()), 2);
}

TEST_F(UTPyCore, ObjectTypes) {
  const auto boolObj = Object::FromNew(PyBool_FromLong(0));
  const auto longObj = Object::FromNew(PyLong_FromLong(42));
  const auto strObj = Object::FromNew(PyUnicode_FromString("test"));
  const auto tupleObj = Object::FromNew(Py_BuildValue("(i, u)", 43, L"second"));

  EXPECT_FALSE(cxxpython::ToBool(boolObj));
  EXPECT_EQ(cxxpython::ToLong(longObj), 42);
  EXPECT_EQ(cxxpython::ToString(strObj), "test");
  EXPECT_EQ(cxxpython::ToLong(tupleObj.at(0)), 43);
  EXPECT_EQ(cxxpython::ToString(tupleObj.at(1)), "second");
}

TEST_F(UTPyCore, LoadModule) {
  EXPECT_TRUE(python.LoadModule(moduleKey, sampleModule));
  EXPECT_TRUE(python.LoadModule(moduleKey, sampleModule));

  EXPECT_FALSE(python.LoadModule({}, {}));
  EXPECT_FALSE(python.LoadModule("42", "42"));
  EXPECT_TRUE(python.LoadModule(moduleKey + "42", sampleModule));
  EXPECT_FALSE(python.LoadModule({}, sampleModule));
}

TEST_F(UTPyCore, PyFunction) {
  EXPECT_TRUE(python.PyFunction({}, {}) == nullptr);
  EXPECT_TRUE(python.PyFunction(moduleKey, {}) == nullptr);

  python.LoadModule(moduleKey, sampleModule);
  EXPECT_TRUE(python.PyFunction(moduleKey, "invalid_func") == nullptr);
  EXPECT_FALSE(python.PyFunction(moduleKey, "get_state") == nullptr);
}

TEST_F(UTPyCore, CallFunction) {
  python.LoadModule(moduleKey, sampleModule);
  const auto get_state = python.PyFunction(moduleKey, "get_state");
  const auto increment = python.PyFunction(moduleKey, "increment");
  const auto add_abc = python.PyFunction(moduleKey, "add_abc");

  EXPECT_EQ(cxxpython::ToLong(get_state()), 42);
  EXPECT_EQ(cxxpython::ToLong(increment("(i)", 42)), 43);
  EXPECT_EQ(cxxpython::ToString(add_abc("(s)", "123")), "123abc");
}