/*
 * The MIT License
 *
 * Copyright (c) 2011 Bruno P. Kinoshita <http://www.kinoshita.eti.br>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @author Bruno P. Kinoshita <http://www.kinoshita.eti.br>
 * @since 0.1
 */

#ifndef TAP_H_
#define TAP_H_

#include <list>
#include <iostream>
#include <map>
#include <fstream>
#include <string>
#include <algorithm>

namespace tap {

#ifdef GTEST_TAP_13_DIAGNOSTIC
// based on http://stackoverflow.com/a/7724536/831180
static std::string replace_all_copy(
  std::string const& original,
  std::string const& before,
  std::string const& after
) {
  using namespace std;

  if (before == after) return string(original);

  string retval;
  if (before.length() == after.length()) retval.reserve(original.size());

  basic_string <char>::const_iterator end = original.end();
  basic_string <char>::const_iterator current = original.begin();
  basic_string <char>::const_iterator next =
    search(current, end, before.begin(), before.end());

  while ( next != end ) {
    retval.append( current, next );
    retval.append( after );
    current = next + before.size();
    next = search(current, end, before.begin(), before.end());
  }
  retval.append( current, next );
  return retval;
}
#endif

class TestResult {

 private:
  int number;
  std::string status;
  std::string name;
  std::string comment;
  bool skip;

 public:
  std::string getComment() const {
    std::stringstream ss;
    if (this->skip) {
      ss << "# SKIP " << this->comment;
    } else if (!this->comment.empty()) {
      ss << "# " << this->comment;
    }
    return ss.str();
  }

  const std::string& getName() const {
    return name;
  }

  int getNumber() const {
    return number;
  }

  const std::string& getStatus() const {
    return status;
  }

  bool getSkip() const {
    return skip;
  }

  void setComment(const std::string& value) {
    this->comment = value;
  }

  void setName(const std::string& value) {
    this->name = value;
  }

  void setNumber(int value) {
    this->number = value;
  }

  void setStatus(const std::string& value) {
    this->status = value;
  }

  void setSkip(bool value) {
    this->skip = value;
  }

  std::string toString() const {
    std::stringstream ss;
    ss << this->status << " " << this->number << " " << this->name;
#ifdef GTEST_TAP_13_DIAGNOSTIC
    std::string comment_text = this->getComment();
    if (!comment_text.empty()) {
      ss << std::endl
       << "# Diagnostic" << std::endl
       << "  ---" << std::endl
       << "  " << replace_all_copy(this->getComment(), "\n", "\n  ");
    }
#endif
    return ss.str();
  }
};

class TestSet {

 private:
  std::list<TestResult> testResults;

 public:
  const std::list<TestResult>& getTestResults() const {
    return testResults;
  }

  void addTestResult(TestResult& testResult) {
    testResult.setNumber((this->getNumberOfTests() + 1));
    this->testResults.push_back(testResult);
  }

  int getNumberOfTests() const {
    return this->testResults.size();
  }

  std::string toString() const {
    std::stringstream ss;
    ss << "1.." << this->getNumberOfTests() << std::endl;
    for (std::list<TestResult>::const_iterator ci = this->testResults.begin();
	 ci != this->testResults.end(); ++ci) {
      TestResult testResult = *ci;
      ss << testResult.toString() << std::endl;
    }
    return ss.str();
  }
};

class TapListener: public ::testing::EmptyTestEventListener {

 private:
  std::map<std::string, tap::TestSet> testCaseTestResultMap;

  void addTapTestResult(const testing::TestInfo& testInfo) {
    tap::TestResult tapResult;
    tapResult.setName(testInfo.name());
    tapResult.setSkip(!testInfo.should_run());

    const testing::TestResult *testResult = testInfo.result();
    int number = testResult->total_part_count();
    tapResult.setNumber(number-1);
    if (testResult->HasFatalFailure()) {
      tapResult.setStatus("Bail out!");
    } else if (testResult->Failed()) {
      tapResult.setStatus("not ok");
      tapResult.setComment(testResult->GetTestPartResult(number-1).summary());
    } else {
      tapResult.setStatus("ok");
    }

    this->addNewOrUpdate(testInfo.test_case_name(), tapResult);
  }

  std::string getCommentOrDirective(const std::string& comment, bool skip) {
    std::stringstream commentText;

    if (skip) {
      commentText << " # SKIP " << comment;
    } else if (!comment.empty()) {
      commentText << " # " << comment;
    }

    return commentText.str();
  }

  void addNewOrUpdate(const std::string& testCaseName, tap::TestResult testResult) {
    std::map<std::string, tap::TestSet>::const_iterator ci =
      this->testCaseTestResultMap.find(testCaseName);
    if (ci != this->testCaseTestResultMap.end()) {
      tap::TestSet testSet = ci->second;
      testSet.addTestResult(testResult);
      this->testCaseTestResultMap[testCaseName] = testSet;
    } else {
      tap::TestSet testSet;
      testSet.addTestResult(testResult);
      this->testCaseTestResultMap[testCaseName] = testSet;
    }
  }

public:
  virtual void OnTestEnd(const testing::TestInfo& testInfo) {
    //printf("%s %d - %s\n", testInfo.result()->Passed() ? "ok" : "not ok", this->testNumber, testInfo.name());
    this->addTapTestResult(testInfo);
  }

  virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) {
    //--- Write the count and the word.
    (void)unit_test;
    std::map<std::string, tap::TestSet>::const_iterator ci;
    for (ci = this->testCaseTestResultMap.begin();
	 ci != this->testCaseTestResultMap.end(); ++ci) {
      const tap::TestSet& testSet = ci->second;
#ifdef GTEST_TAP_PRINT_TO_STDOUT
      std::cout << "TAP version 13" << std::endl;
      std::cout << testSet.toString();
#else
      std::string ext = ".tap";
      std::ofstream tapFile;
      tapFile.open((ci->first + ext).c_str());
      tapFile << testSet.toString();
      tapFile.close();
#endif
    }
  }
};

} // namespace tap

#endif // TAP_H_
