// Copyright 2018 The Amber Authors.
//
// 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 "src/tokenizer.h"

#include <cmath>
#include <limits>

#include "gtest/gtest.h"

namespace amber {

using TokenizerTest = testing::Test;

TEST_F(TokenizerTest, ProcessEmpty) {
  Tokenizer t("");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessIdentifier) {
  Tokenizer t("TestIdentifier");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("TestIdentifier", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessInt) {
  Tokenizer t("123");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(123U, next->AsUint32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessNegative) {
  Tokenizer t("-123");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(-123, next->AsInt32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessDouble) {
  Tokenizer t("123.456");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(123.456f, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

namespace {

void TestNaN(const std::string& nan_str) {
  Tokenizer t(nan_str);
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_TRUE(std::isnan(next->AsDouble()));

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

}  // namespace

TEST_F(TokenizerTest, ProcessNaN) {
  TestNaN("nan");
  TestNaN("naN");
  TestNaN("nAn");
  TestNaN("nAN");
  TestNaN("Nan");
  TestNaN("NaN");
  TestNaN("NAn");
  TestNaN("NAN");
}

TEST_F(TokenizerTest, ProcessNegativeDouble) {
  Tokenizer t("-123.456");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(-123.456f, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessDoubleStartWithDot) {
  Tokenizer t(".123456");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(.123456f, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessStringWithNumberInName) {
  Tokenizer t("BufferAccess32");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("BufferAccess32", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessMultiStatement) {
  Tokenizer t("TestValue 123.456");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("TestValue", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(123.456f, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessMultiLineStatement) {
  Tokenizer t("TestValue 123.456\nAnotherValue\n\nThirdValue 456");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("TestValue", next->AsString());
  EXPECT_EQ(1U, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(123.456f, next->AsFloat());
  EXPECT_EQ(1U, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("AnotherValue", next->AsString());
  EXPECT_EQ(2U, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("ThirdValue", next->AsString());
  EXPECT_EQ(4U, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(456U, next->AsUint16());
  EXPECT_EQ(4U, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ProcessComments) {
  Tokenizer t(R"(# Initial comment string
TestValue 123.456
    AnotherValue   # Space before, comment after

ThirdValue 456)");
  auto next = t.NextToken();
  // The comment injects a blank line into the output
  // so we can handle full line comment and end of line comment the same.
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("TestValue", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_EQ(123.456f, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("AnotherValue", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOL());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("ThirdValue", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(456U, next->AsUint16());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, HexValue) {
  Tokenizer t("0xff00f0ff");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsHex());
  EXPECT_EQ(0xff00f0ff, next->AsHex());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, HexValueAfterWhiteSpace) {
  Tokenizer t("     \t  \t   0xff00f0ff");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsHex());
  EXPECT_EQ(0xff00f0ff, next->AsHex());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, StringStartingWithNum) {
  Tokenizer t("1/ABC");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(1U, next->AsUint32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("/ABC", next->AsString());
}

TEST_F(TokenizerTest, StringQuotedSingleLine) {
  Tokenizer t("\"Hello world\"");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsString());
  EXPECT_EQ("Hello world", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, StringQuotedMultiLine) {
  Tokenizer t("\"Hello\n\nworld\"");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsString());
  EXPECT_EQ("Hello\n\nworld", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, StringQuotedUnterminated) {
  Tokenizer t("\"Hello\n\nworld");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsString());
  EXPECT_EQ("Hello\n\nworld", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, StringQuotedEscapeSequences) {
  Tokenizer t(R"("_\"\\_a\aa_b\bb_t\tt_n\nn_v\vv_f\ff_r\rr_")");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsString());

  std::string expect = "_\"\\_a\aa_b\bb_t\tt_n\nn_v\vv_f\ff_r\rr_";
  EXPECT_EQ(expect, next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, BracketsAndCommas) {
  Tokenizer t("(1.0, 2, abc)");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsOpenBracket());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsDouble());
  EXPECT_FLOAT_EQ(1.0, next->AsFloat());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsComma());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsInteger());
  EXPECT_EQ(2U, next->AsUint32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsComma());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("abc", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsCloseBracket());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, TokenToDoubleFromDouble) {
  Tokenizer t("-1.234");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsDouble());

  Result r = next->ConvertToDouble();
  ASSERT_TRUE(r.IsSuccess());
  EXPECT_FLOAT_EQ(-1.234f, next->AsFloat());
}

TEST_F(TokenizerTest, TokenToDoubleFromInt) {
  Tokenizer t("-1");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());

  Result r = next->ConvertToDouble();
  ASSERT_TRUE(r.IsSuccess());
  EXPECT_FLOAT_EQ(-1.0f, next->AsFloat());
}

TEST_F(TokenizerTest, DashToken) {
  Tokenizer t("-");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsIdentifier());
  EXPECT_EQ("-", next->AsString());
}

TEST_F(TokenizerTest, ParseUint64Max) {
  Tokenizer t(std::to_string(std::numeric_limits<uint64_t>::max()));
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(std::numeric_limits<uint64_t>::max(), next->AsUint64());
}

TEST_F(TokenizerTest, ParseInt64Min) {
  Tokenizer t(std::to_string(std::numeric_limits<int64_t>::min()));
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(std::numeric_limits<int64_t>::min(), next->AsInt64());
}

TEST_F(TokenizerTest, TokenToDoubleFromUint64Max) {
  Tokenizer t(std::to_string(std::numeric_limits<uint64_t>::max()));
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());

  Result r = next->ConvertToDouble();
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("uint64_t value too big to fit in double", r.Error());
}

TEST_F(TokenizerTest, TokenToDoubleFromInt64Min) {
  Tokenizer t(std::to_string(std::numeric_limits<int64_t>::min()));
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());

  Result r = next->ConvertToDouble();
  ASSERT_TRUE(r.IsSuccess());
  EXPECT_DOUBLE_EQ(static_cast<double>(std::numeric_limits<int64_t>::min()),
                   next->AsDouble());
}

TEST_F(TokenizerTest, TokenToDoubleFromInt64Max) {
  Tokenizer t(std::to_string(std::numeric_limits<int64_t>::max()));
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());

  Result r = next->ConvertToDouble();
  ASSERT_TRUE(r.IsSuccess());
  EXPECT_DOUBLE_EQ(static_cast<double>(std::numeric_limits<int64_t>::max()),
                   next->AsDouble());
}

TEST_F(TokenizerTest, TokenToDoubleFromString) {
  Tokenizer t("INVALID");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsIdentifier());

  Result r = next->ConvertToDouble();
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("Invalid conversion to double", r.Error());
}

TEST_F(TokenizerTest, TokenToDoubleFromHex) {
  Tokenizer t("0xff00f0ff");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsHex());

  Result r = next->ConvertToDouble();
  ASSERT_TRUE(r.IsSuccess());
  EXPECT_FLOAT_EQ(static_cast<float>(0xff00f0ff), next->AsFloat());
}

TEST_F(TokenizerTest, TokenToDoubleFromEOS) {
  Tokenizer t("");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsEOS());

  Result r = next->ConvertToDouble();
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("Invalid conversion to double", r.Error());
}

TEST_F(TokenizerTest, TokenToDoubleFromEOL) {
  Tokenizer t("-1\n-2");
  auto next = t.NextToken();
  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsEOL());

  Result r = next->ConvertToDouble();
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("Invalid conversion to double", r.Error());
}

TEST_F(TokenizerTest, Continuations) {
  Tokenizer t("1 \\\n2");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(1, next->AsInt32());
  EXPECT_EQ(1u, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(2, next->AsInt32());
  EXPECT_EQ(2u, t.GetCurrentLine());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ContinuationAtEndOfString) {
  Tokenizer t("1 \\");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(1, next->AsInt32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsIdentifier());
  EXPECT_EQ("\\", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ContinuationTokenAtOfLine) {
  Tokenizer t("1 \\2");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(1, next->AsInt32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsIdentifier());
  EXPECT_EQ("\\2", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ContinuationTokenInMiddleOfLine) {
  Tokenizer t("1 \\ 2");
  auto next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(1, next->AsInt32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsIdentifier());
  EXPECT_EQ("\\", next->AsString());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  ASSERT_TRUE(next->IsInteger());
  EXPECT_EQ(2u, next->AsInt32());

  next = t.NextToken();
  ASSERT_TRUE(next != nullptr);
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ExtractToNext) {
  Tokenizer t("this\nis\na\ntest\nEND");

  auto next = t.NextToken();
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("this", next->AsString());

  std::string s = t.ExtractToNext("END");
  ASSERT_EQ("\nis\na\ntest\n", s);

  next = t.NextToken();
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("END", next->AsString());
  EXPECT_EQ(5U, t.GetCurrentLine());

  next = t.NextToken();
  EXPECT_TRUE(next->IsEOS());
}

TEST_F(TokenizerTest, ExtractToNextMissingNext) {
  Tokenizer t("this\nis\na\ntest\n");

  auto next = t.NextToken();
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("this", next->AsString());

  std::string s = t.ExtractToNext("END");
  ASSERT_EQ("\nis\na\ntest\n", s);

  next = t.NextToken();
  EXPECT_TRUE(next->IsEOS());
  EXPECT_EQ(5U, t.GetCurrentLine());
}

TEST_F(TokenizerTest, ExtractToNextCurrentIsNext) {
  Tokenizer t("END");
  std::string s = t.ExtractToNext("END");
  ASSERT_EQ("", s);

  auto next = t.NextToken();
  EXPECT_TRUE(next->IsIdentifier());
  EXPECT_EQ("END", next->AsString());

  next = t.NextToken();
  EXPECT_TRUE(next->IsEOS());
}

}  // namespace amber
