/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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.
 */

#include "../lua_engine.h"

#include <gtest/gtest.h>

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace lua_interpreter {
namespace tests {

// The fixture for testing class LuaEngine
class LuaEngineTest : public testing::Test {
 protected:
  lua_interpreter::LuaEngine lua_engine_;

  std::string ConvertVectorToString(std::vector<std::string> vector) {
    std::stringstream output;
    for (std::string s : vector) {
      output << s;
    }
    return output.str();
  }

  std::string ConvertArrayToString(char** array, int size) {
    std::stringstream output;
    for (int i = 0; i < size; i++) {
      output << array[i];
    }
    return output.str();
  }
};

TEST_F(LuaEngineTest, ExecuteScriptLogCallback) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) log('Logging here') end", "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("LUA: Logging here"), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnSuccessMoreArguments) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_success({}, {}) end", "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("on_success can push only a single parameter from "
                        "Lua - a Lua table"),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnSuccessNonTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_success('Success!') end", "test", "{}",
      "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("on_success can push only a single parameter from "
                        "Lua - a Lua table"),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnSuccessWithPopulatedTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; tbl['sessionId'] = 1; "
      "on_success(tbl) end",
      "test", "{}", "{}");
  std::string saved_state = lua_engine_.GetSavedState();
  EXPECT_EQ("{\"sessionId\":1}\n", saved_state);
}

TEST_F(LuaEngineTest, ExecuteScriptOnSuccessWithEmptyTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; "
      "on_success(tbl) end",
      "test", "{}", "{}");
  std::string saved_state = lua_engine_.GetSavedState();
  EXPECT_EQ("[]\n", saved_state);
}

TEST_F(LuaEngineTest, ExecuteScriptOnScriptFinishedMoreArguments) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_script_finished({}, {}) end", "test", "{}",
      "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("on_script_finished can push only a single parameter "
                        "from Lua - a Lua "
                        "table"),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnScriptFinishedNonTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_script_finished('Script "
      "finished') end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("on_script_finished can push only a single parameter "
                        "from Lua - a Lua "
                        "table"),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnScriptFinishedWithTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; tbl['sessionId'] = 1; "
      "on_script_finished(tbl) "
      "end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("{\"sessionId\":1}"), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnErrorMoreArguments) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_error('ERROR ONE', 'ERROR "
      "TWO') end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find("on_error can push only a single string parameter from Lua"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnErrorNonString) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_error({}) end", "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find("on_error can push only a single string parameter from Lua"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnErrorWithSingleString) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_error('ERROR: 2') end", "test", "{}",
      "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("ERROR: 2"), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportMoreArguments) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_metrics_report({}, {}, {}) end", "test",
      "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find(
          "on_metrics_report should push 1 to 2 parameters of Lua table type. "
          "The first table is a metrics report and the second is an optional "
          "state to save"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportNonTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_metrics_report('Incoming "
      "metrics report') "
      "end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find(
          "on_metrics_report should push 1 to 2 parameters of Lua table type. "
          "The first table is a metrics report and the second is an optional "
          "state to save"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportNonTableWithTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_metrics_report('Incoming "
      "metrics report', "
      "{}) end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find(
          "on_metrics_report should push 1 to 2 parameters of Lua table type. "
          "The first table is a metrics report and the second is an optional "
          "state to save"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportTableWithNonTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) on_metrics_report({}, 'Saved "
      "state here') "
      "end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(
      actual.find(
          "on_metrics_report should push 1 to 2 parameters of Lua table "
          "type. "
          "The first table is a metrics report and the second is an optional "
          "state to save"),
      std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportSingleTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; tbl['sessionId'] = 1; "
      "on_metrics_report(tbl) "
      "end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("{\"sessionId\":1}"), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptOnMetricsReportMultipleTable) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; tbl['sessionId'] = 1; "
      "on_metrics_report(tbl, "
      "tbl) end",
      "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_EQ("{\"sessionId\":1}\n", actual);
  EXPECT_EQ("{\"sessionId\":1}\n", lua_engine_.GetSavedState());
}

TEST_F(LuaEngineTest, ExecuteScriptWithPreviousState) {
  lua_engine_.ExecuteScript(
      "function test(data, state) tbl = {}; tbl['result'] = state.data + 1; "
      "on_success(tbl) end",
      "test", "{}", "{\"data\": 1}");
  std::string saved_state = lua_engine_.GetSavedState();
  EXPECT_EQ("{\"result\":2}\n", saved_state);
}

TEST_F(LuaEngineTest, ExecuteScriptWrongFunctionName) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) end", "tesT", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Wrong function name."), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptSyntaxError) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) end f", "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error encountered while loading the script."),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptSyntaxErrorInsideFunction) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) x == 1 end", "test", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error encountered while loading the script."),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptRuntimeError) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) unknown_function(data, state) end", "test",
      "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error encountered while running the script."),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptRuntimeErrorWithStackTrace) {
  const char* script = "function func_2(data, state)\n"
                       "    func_3(data, state)\n"
                       "end\n"
                       "function func_1(data, state)\n"
                       "    func_2(data, state)\n"
                       "end";
  std::vector<std::string> output = lua_engine_.ExecuteScript(script, "func_1", "{}", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error encountered while running the script."), std::string::npos);
  EXPECT_NE(actual.find("func_3"), std::string::npos);
  EXPECT_NE(actual.find("func_2"), std::string::npos);
  EXPECT_NE(actual.find("func_1"), std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptInvalidPublishedData) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) end", "test", "invalid", "{}");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error from parsing published data"),
            std::string::npos);
}

TEST_F(LuaEngineTest, ExecuteScriptInvalidSavedState) {
  std::vector<std::string> output = lua_engine_.ExecuteScript(
      "function test(data, state) end", "test", "{}", "invalid");
  std::string actual = ConvertVectorToString(output);
  EXPECT_NE(actual.find("Error from parsing saved state"), std::string::npos);
}

TEST_F(LuaEngineTest, StringVectorToArrayEmpty) {
  std::vector<std::string> vector = {};
  char** array = LuaEngine::StringVectorToCharArray(vector);
  EXPECT_EQ(nullptr, array);
}

TEST_F(LuaEngineTest, StringVectorToArrayNonEmpty) {
  std::vector<std::string> vector = {"1", "2", "3", "4"};
  char** array = LuaEngine::StringVectorToCharArray(vector);
  EXPECT_EQ("1234", ConvertArrayToString(array, 4));
}
}  // namespace tests
}  // namespace lua_interpreter

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}