//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//
//============================================================================

#ifndef tests_SchemaValidator_H
#define tests_SchemaValidator_H

#include <memory>
#include <string>

#include <fides_rapidjson.h>
// clang-format off
#include FIDES_RAPIDJSON(rapidjson/error/en.h)
#include FIDES_RAPIDJSON(rapidjson/filereadstream.h)
#include FIDES_RAPIDJSON(rapidjson/prettywriter.h)
#include FIDES_RAPIDJSON(rapidjson/schema.h)
#include FIDES_RAPIDJSON(rapidjson/stringbuffer.h)
// clang-format on

class SchemaValidator
{
public:
  // Sets up the schema document
  SchemaValidator(const std::string& schemaFilename)
  {
    FILE* schemafp = std::fopen(schemaFilename.c_str(), "rb");
    if (!schemafp)
    {
      throw std::ios_base::failure("Unable to open schema file; Does " + schemaFilename + "exist?");
    }

    std::vector<char> schemaBuf(65536);
    rapidjson::FileReadStream schemaStream(schemafp, schemaBuf.data(), schemaBuf.size());
    rapidjson::Document d;
    d.ParseStream(schemaStream);
    if (d.HasParseError())
    {
      std::cerr << "Schema file" << schemaFilename << "is not a valid JSON " << std::endl;
      std::cerr << rapidjson::GetParseError_En(d.GetParseError()) << std::endl;
    }

    // Then convert the Document into SchemaDocument
    this->SchemaDoc.reset(new rapidjson::SchemaDocument(d));
  }

  // opens the JSON file to be validated and checks for parsing errors
  bool SetJSONFile(const std::string& jsonFilename)
  {
    FILE* jsonfp = std::fopen(jsonFilename.c_str(), "rb");
    if (!jsonfp)
    {
      throw std::ios_base::failure("Unable to open JSON file; Does " + jsonFilename + "exist?");
    }

    std::vector<char> jsonBuf(65536);
    rapidjson::FileReadStream jsonStream(jsonfp, jsonBuf.data(), jsonBuf.size());
    this->JSONDoc.ParseStream(jsonStream);
    if (this->JSONDoc.HasParseError())
    {
      std::cerr << "JSON file " << jsonFilename << "is not a valid JSON" << std::endl;
      std::cerr << rapidjson::GetParseError_En(this->JSONDoc.GetParseError()) << std::endl;
      return false;
    }
    return true;
  }

  // Similar to SetJSONFile but accepts a string that contains JSON
  bool SetDocument(const std::string& json)
  {
    rapidjson::StringStream s(json.c_str());
    this->JSONDoc.ParseStream(s);
    if (this->JSONDoc.HasParseError())
    {
      std::cerr << "The JSON string passed is not a valid JSON" << std::endl;
      std::cerr << rapidjson::GetParseError_En(this->JSONDoc.GetParseError()) << std::endl;
      return false;
    }
    return true;
  }

  // validates the JSON against the provided schema
  bool Validate()
  {
    rapidjson::SchemaValidator validator(*SchemaDoc);
    if (!this->JSONDoc.Accept(validator))
    {
      // Input JSON is invalid according to the schema
      // Output diagnostic information
      rapidjson::StringBuffer sb;
      validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
      std::cout << "Invalid schema: " << sb.GetString() << std::endl;
      std::cout << "Invalid keyword: " << validator.GetInvalidSchemaKeyword() << std::endl;
      sb.Clear();
      validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
      std::cout << "Invalid document: " << sb.GetString() << std::endl;
      return false;
    }
    else
    {
      return true;
    }
  }

private:
  std::shared_ptr<rapidjson::SchemaDocument> SchemaDoc;
  rapidjson::Document JSONDoc;
};

#endif
