/*
 * Modern C++ JSON schema validator
 *
 * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
 *
 * Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
 *
 * 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.
 */
#ifndef NLOHMANN_JSON_SCHEMA_HPP__
#define NLOHMANN_JSON_SCHEMA_HPP__

/*
#ifdef _WIN32
#    ifdef JSON_SCHEMA_VALIDATOR_EXPORTS
#        define JSON_SCHEMA_VALIDATOR_API __declspec(dllexport)
#    else
#        define JSON_SCHEMA_VALIDATOR_API __declspec(dllimport)
#    endif
#else
#    define JSON_SCHEMA_VALIDATOR_API
#endif
*/
#    define JSON_SCHEMA_VALIDATOR_API

#include <json.hpp>

// make yourself a home - welcome to nlohmann's namespace
namespace nlohmann
{

// a class representing a JSON-pointer RFC6901
//
// examples of JSON pointers
//
//   #     - root of the current document
//   #item - refers to the object which is identified ("id") by `item`
//           in the current document
//   #/path/to/element
//         - refers to the element in /path/to from the root-document
//
//
// The json_pointer-class stores everything in a string, which might seem bizarre
// as parsing is done from a string to a string, but from_string() is also
// doing some formatting.
//
// TODO
//   ~ and %  - codec
//   needs testing and clarification regarding the '#' at the beginning

class json_pointer
{
	std::string str_;

	void from_string(const std::string &r);

public:
	json_pointer(const std::string &s = "")
	{
		from_string(s);
	}

	void append(const std::string &elem)
	{
		str_.append(elem);
	}

	const std::string &to_string() const
	{
		return str_;
	}
};

// A class representing a JSON-URI for schemas derived from
// section 8 of JSON Schema: A Media Type for Describing JSON Documents
// draft-wright-json-schema-00
//
// New URIs can be derived from it using the derive()-method.
// This is useful for resolving refs or subschema-IDs in json-schemas.
//
// This is done implement the requirements described in section 8.2.
//
class JSON_SCHEMA_VALIDATOR_API json_uri
{
	std::string urn_;

	std::string proto_;
	std::string hostname_;
	std::string path_;
	json_pointer pointer_;

protected:
	// decodes a JSON uri and replaces all or part of the currently stored values
	void from_string(const std::string &uri);

	std::tuple<std::string, std::string, std::string, std::string, std::string> tie() const
	{
		return std::tie(urn_, proto_, hostname_, path_, pointer_.to_string());
	}

public:
	json_uri(const std::string &uri)
	{
		from_string(uri);
	}

	const std::string protocol() const { return proto_; }
	const std::string hostname() const { return hostname_; }
	const std::string path() const { return path_; }
	const json_pointer pointer() const { return pointer_; }

	const std::string url() const;

	// decode and encode strings for ~ and % escape sequences
	static std::string unescape(const std::string &);
	static std::string escape(const std::string &);

	// create a new json_uri based in this one and the given uri
	// resolves relative changes (pathes or pointers) and resets part if proto or hostname changes
	json_uri derive(const std::string &uri) const
	{
		json_uri u = *this;
		u.from_string(uri);
		return u;
	}

	// append a pointer-field to the pointer-part of this uri
	json_uri append(const std::string &field) const
	{
		json_uri u = *this;
		u.pointer_.append("/" + field);
		return u;
	}

	std::string to_string() const;

	friend bool operator<(const json_uri &l, const json_uri &r)
	{
		return l.tie() < r.tie();
	}

	friend bool operator==(const json_uri &l, const json_uri &r)
	{
		return l.tie() == r.tie();
	}

	friend std::ostream &operator<<(std::ostream &os, const json_uri &u);
};

namespace json_schema_draft4
{

extern json draft4_schema_builtin;

class JSON_SCHEMA_VALIDATOR_API json_validator
{
	std::vector<std::shared_ptr<json>> schema_store_;
	std::shared_ptr<json> root_schema_;
	std::function<void(const json_uri &, json &)> schema_loader_ = nullptr;
	std::function<void(const std::string &, const std::string &)> format_check_ = nullptr;

	std::map<json_uri, const json *> schema_refs_;

	void validate(const json &instance, const json &schema_, const std::string &name);
	void validate_array(const json &instance, const json &schema_, const std::string &name);
	void validate_object(const json &instance, const json &schema_, const std::string &name);
    void validate_string(const json &instance, const json &schema, const std::string &name);

	void insert_schema(const json &input, const json_uri &id);

public:
	json_validator(std::function<void(const json_uri &, json &)> loader = nullptr,
	               std::function<void(const std::string &, const std::string &)> format = nullptr)
	    : schema_loader_(loader), format_check_(format)
	{
	}

	// insert and set a root-schema
	void set_root_schema(const json &);

	// validate a json-document based on the root-schema
	void validate(const json &instance);
};

} // json_schema_draft4
} // nlohmann

#endif /* NLOHMANN_JSON_SCHEMA_HPP__ */
