/* 
   Mathieu Stefani, 24 février 2016
   
   An API description (reflection) mechanism that is based on Swagger
*/

#pragma once

#include <cstdint>
#include <string>
#include <vector>
#include <type_traits>
#include <memory>
#include <algorithm>

#include <pistache/http_defs.h>
#include <pistache/mime.h>
#include <pistache/optional.h>
#include <pistache/router.h>
#include <pistache/iterator_adapter.h>

namespace Pistache {
namespace Rest {
namespace Type {

// Data Types

#define DATA_TYPE \
    TYPE(Integer, std::int32_t             , "integer", "int32")  \
    TYPE(Long   , std::int64_t             , "integer", "int64")  \
    TYPE(Float  , float                    , "number" , "float")  \
    TYPE(Double , double                   , "number" , "double") \
    TYPE(String , std::string              , "string" , "")       \
    TYPE(Byte   , char                     , "string" , "byte")   \
    TYPE(Binary , std::vector<std::uint8_t>, "string" , "binary") \
    TYPE(Bool   , bool                     , "boolean", "")       \
    COMPLEX_TYPE(Date    , "string", "date")                      \
    COMPLEX_TYPE(Datetime, "string", "date-time")                 \
    COMPLEX_TYPE(Password, "string", "password")                  \
    COMPLEX_TYPE(Array   , "array",  "array")

#define TYPE(rest, cpp, _, __) \
    typedef cpp rest;
#define COMPLEX_TYPE(rest, _, __) \
    struct rest { };
    DATA_TYPE
#undef TYPE
#undef COMPLEX_TYPE

} // namespace Type

#define SCHEMES            \
    SCHEME(Http , "http")  \
    SCHEME(Https, "https") \
    SCHEME(Ws   , "ws")    \
    SCHEME(Wss  , "wss")   \


enum class Scheme {
#define SCHEME(e, _) e,
    SCHEMES
#undef SCHEME
};

const char* schemeString(Scheme scheme);

namespace Schema {

namespace Traits {

template<typename DT> struct IsDataType : public std::false_type { };

#define TYPE(rest, _, __, ___) \
    template<> struct IsDataType<Type::rest> : public std::true_type { };
#define COMPLEX_TYPE(rest, _, __) \
    template<> struct IsDataType<Type::rest> : public std::true_type { };
    DATA_TYPE
#undef TYPE
#undef COMPLEX_TYPE

template<typename DT> struct DataTypeInfo;

#define TYPE(rest, _, typeStr, formatStr)                       \
    template<> struct DataTypeInfo<Type::rest> {                \
        static const char *typeName() { return typeStr; } \
        static const char *format() { return formatStr; } \
    };
#define COMPLEX_TYPE(rest, typeStr, formatStr)                 \
    template<> struct DataTypeInfo<Type::rest> {                \
        static const char* typeName() { return typeStr; } \
        static const char* format() { return formatStr; } \
    };
    DATA_TYPE
#undef TYPE
#undef COMPLEX_TYPE

template<typename DT> struct DataTypeValidation {
    static bool validate(const std::string& ) {
        return true;
    }
};

} // namespace Traits

struct ProduceConsume {
    ProduceConsume()
    : produce()
    , consume()
    { }

    std::vector<Http::Mime::MediaType> produce;
    std::vector<Http::Mime::MediaType> consume;
};

struct Contact {
    Contact(std::string name, std::string url, std::string email);

    std::string name;
    std::string url;
    std::string email;
};

struct License {
    License(std::string name, std::string url);

    std::string name;
    std::string url;
};

struct Info {
    Info(std::string title, std::string version, std::string description = "");

    std::string title;
    std::string version;
    std::string description;
    std::string termsOfService;

    Optional<Contact> contact;
    Optional<License> license;
};

struct InfoBuilder {
    InfoBuilder(Info* info);

    InfoBuilder& termsOfService(std::string value);
    InfoBuilder& contact(std::string name, std::string url, std::string email);
    InfoBuilder& license(std::string name, std::string url);

private:
    Info *info_;
};


struct DataType {
    virtual const char* typeName() const = 0;
    virtual const char* format() const = 0;

    virtual bool validate(const std::string& input) const = 0;

    virtual ~DataType() {}
};

template<typename T>
struct DataTypeT : public DataType {
    const char* typeName() const { return Traits::DataTypeInfo<T>::typeName(); }
    const char* format() const { return Traits::DataTypeInfo<T>::format(); }

    bool validate(const std::string& input) const { return Traits::DataTypeValidation<T>::validate(input); }

    virtual ~DataTypeT() {}
};

template<typename T>
std::unique_ptr<DataType> makeDataType() {
    static_assert(Traits::IsDataType<T>::value, "Unknown Data Type");
    return std::unique_ptr<DataType>(new DataTypeT<T>());
}

struct Parameter {
    Parameter(
            std::string name, std::string description);

    template<typename T, typename... Args>
    static Parameter create(Args&& ...args) {
        Parameter p(std::forward<Args>(args)...);
        p.type = makeDataType<T>();
        return p;
    }

    std::string name;
    std::string description;
    bool required;
    std::shared_ptr<DataType> type;

};

struct Response {
    Response(Http::Code statusCode, std::string description);

    Http::Code statusCode;
    std::string description;
};

struct ResponseBuilder {
    ResponseBuilder(Http::Code statusCode, std::string description);

    operator Response() const { return response_; }

private:
    Response response_;
};

struct PathDecl {
    PathDecl(std::string value, Http::Method method);

    std::string value;
    Http::Method method;
};

struct Path {
    Path(std::string value, Http::Method method, std::string description);

    std::string value;
    Http::Method method;
    std::string description;
    bool hidden;

    ProduceConsume pc;
    std::vector<Parameter> parameters;
    std::vector<Response> responses;

    Route::Handler handler;

    static std::string swaggerFormat(const std::string& path);

    bool isBound() const {
        return handler != nullptr;
    }
};

class PathGroup {
public:
    struct Group : public std::vector<Path> {
        bool isHidden() const {
            if (empty()) return false;

            return std::all_of(begin(), end(), [](const Path& path) {
                return path.hidden;
            });
        }
    };

    typedef std::unordered_map<std::string, Group> Map;
    typedef Map::iterator iterator;
    typedef Map::const_iterator const_iterator;

    typedef std::vector<Path>::iterator group_iterator;

    typedef FlatMapIteratorAdapter<Map> flat_iterator;

    enum class Format { Default, Swagger };

    bool hasPath(const std::string& name, Http::Method method) const;
    bool hasPath(const Path& path) const;

    PathGroup()
      : groups_()
    { }

    Group paths(const std::string& name) const;
    Optional<Path> path(const std::string& name, Http::Method method) const;

    group_iterator add(Path path);

    template<typename... Args>
    group_iterator emplace(Args&& ...args) {
        return add(Path(std::forward<Args>(args)...));
    }

    const_iterator begin() const;
    const_iterator end() const;

    flat_iterator flatBegin() const;
    flat_iterator flatEnd() const;

    Map groups() const { return groups_; }

private:
    Map groups_;
};

struct PathBuilder {
    PathBuilder(Path* path);

    template<typename... Mimes>
    PathBuilder& produces(Mimes... mimes) {
        Http::Mime::MediaType m[sizeof...(Mimes)] = { mimes... };
        std::copy(std::begin(m), std::end(m), std::back_inserter(path_->pc.produce));
        return *this;
    }

    template<typename... Mimes>
    PathBuilder& consumes(Mimes... mimes) {
        Http::Mime::MediaType m[sizeof...(Mimes)] = { mimes... };
        std::copy(std::begin(m), std::end(m), std::back_inserter(path_->pc.consume));
        return *this;
    }

    template<typename T>
    PathBuilder& parameter(std::string name, std::string description) {
        path_->parameters.push_back(Parameter::create<T>(std::move(name), std::move(description)));
        return *this;
    }

    PathBuilder& response(Http::Code statusCode, std::string description) {
        path_->responses.push_back(Response(statusCode, std::move(description)));
        return *this;
    }

    PathBuilder& response(Response response) {
        path_->responses.push_back(std::move(response));
        return *this;
    }

    /* @CodeDup: should re-use Routes::bind */
    template<typename Result, typename Cls, typename... Args, typename Obj>
    PathBuilder& bind(Result (Cls::*func)(Args...), Obj obj) {

        #define CALL_MEMBER_FN(obj, pmf)  ((obj)->*(pmf))

        path_->handler = [=](const Rest::Request& request, Http::ResponseWriter response) {
            CALL_MEMBER_FN(obj, func)(request, std::move(response));

            return Route::Result::Ok;
        };

        #undef CALL_MEMBER_FN

        return *this;
    }

    template<typename Result, typename... Args>
    PathBuilder& bind(Result (*func)(Args...)) {

        path_->handler = [=](const Rest::Request& request, Http::ResponseWriter response) {
            func(request, std::move(response));

            return Route::Result::Ok;
        };

        return *this;
    }

    PathBuilder&
    hide(bool value = true) {
        path_->hidden = value;
        return *this;
    }

private:
    Path* path_;
};

struct SubPath {
    SubPath(std::string prefix, PathGroup* paths);

    PathBuilder route(std::string name, Http::Method method, std::string description = "");
    PathBuilder route(PathDecl fragment, std::string description = "");

    SubPath path(std::string prefix);

    template<typename T>
    void parameter(std::string name, std::string description) {
        parameters.push_back(Parameter::create<T>(std::move(name), std::move(description)));
    }

    std::string prefix;
    std::vector<Parameter> parameters;
    PathGroup* paths;
};

} // namespace Schema

class Description {
public:
    Description(std::string title, std::string version, std::string description = "");

    Schema::InfoBuilder info();

    Description& host(std::string value);
    Description& basePath(std::string value);

    template<typename... Schemes>
    Description& schemes(Schemes... _schemes) {
        Scheme s[sizeof...(Schemes)] = { _schemes... };
        std::copy(std::begin(s), std::end(s), std::back_inserter(schemes_));
        return *this;
    }

    template<typename... Mimes>
    Description& produces(Mimes... mimes) {
        Http::Mime::MediaType m[sizeof...(Mimes)] = { mimes... };
        std::copy(std::begin(m), std::end(m), std::back_inserter(pc.produce));
        return *this;
    }

    template<typename... Mimes>
    Description& consumes(Mimes... mimes) {
        Http::Mime::MediaType m[sizeof...(Mimes)] = { mimes... };
        std::copy(std::begin(m), std::end(m), std::back_inserter(pc.consume));
        return *this;
    }

    Schema::PathDecl options(std::string name);
    Schema::PathDecl get(std::string name);
    Schema::PathDecl post(std::string name);
    Schema::PathDecl head(std::string name);
    Schema::PathDecl put(std::string name);
    Schema::PathDecl patch(std::string name);
    Schema::PathDecl del(std::string name);
    Schema::PathDecl trace(std::string name);
    Schema::PathDecl connect(std::string name);

    Schema::SubPath path(std::string name);

    Schema::PathBuilder route(std::string name, Http::Method method, std::string description = "");
    Schema::PathBuilder route(Schema::PathDecl fragment, std::string description = "");

    Schema::ResponseBuilder response(Http::Code statusCode, std::string description);

    Schema::Info rawInfo() const { return info_; }
    std::string rawHost() const { return host_; }
    std::string rawBasePath() const { return basePath_; }
    std::vector<Scheme> rawSchemes() const { return schemes_; }
    Schema::ProduceConsume rawPC() const { return pc; }
    Schema::PathGroup rawPaths() const { return paths_; }

private:
    Schema::Info info_;
    std::string host_;
    std::string basePath_;
    std::vector<Scheme> schemes_;
    Schema::ProduceConsume pc;

    Schema::PathGroup paths_;
};

class Swagger {
public:
    Swagger(const Description& description)
        : description_(description)
        , uiPath_()
        , uiDirectory_()
        , apiPath_()
        , serializer_()
    { }

    typedef std::function<std::string (const Description&)> Serializer;

    Swagger& uiPath(std::string path);
    Swagger& uiDirectory(std::string dir);
    Swagger& apiPath(std::string path);
    Swagger& serializer(Serializer serialize);

    void install(Rest::Router& router);

private:
    Description description_;
    std::string uiPath_;
    std::string uiDirectory_;
    std::string apiPath_;
    Serializer serializer_;
};

} // namespace Rest
} // namespace Pistache
