// -*- mode: C++ -*-
// Copyright 2017-2019 The Procyon 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.

#ifndef PN_INPUT_
#define PN_INPUT_

#include <pn/procyon.h>
#include <pn/arg>
#include <pn/array>
#include <pn/data>
#include <pn/map>
#include <pn/string>
#include <pn/value>
#include <type_traits>

namespace pn {

extern input in;

class input {
  public:
    input() : _c_obj{pn_file_input(nullptr)} {}
    explicit input(string_view path, text_mode mode);
    explicit input(FILE* f) : _c_obj{pn_file_input(f)} {}
    explicit input(pn_input_t in) : _c_obj{in} {}
    input(const input&) = delete;
    input(input&& other) : _c_obj{pn_file_input(nullptr)} { std::swap(_c_obj, other._c_obj); }
    input& operator=(const input&) = delete;
    input& operator=(input&& other) { return std::swap(_c_obj, other._c_obj), *this; }
    ~input() { pn_input_close(&_c_obj); }

           operator bool() const { return c_obj()->type && !eof() && !error(); }
    input& check() &;
    input  check() && { return check(), std::move(*this); }

    template <typename... arguments>
    input& read(arguments... args);

    bool error() const { return pn_input_error(c_obj()); }
    bool eof() const { return pn_input_eof(c_obj()); }

    pn_input_t*       c_obj() { return &_c_obj; }
    const pn_input_t* c_obj() const { return &_c_obj; }

  private:
    void check_ok();

    pn_input_t _c_obj;
};

class input_view {
  public:
    input_view() : _c_obj{pn_file_input(nullptr)} {}
    input_view(const input& in) : _c_obj{*in.c_obj()} {}
    input_view(FILE* f) : _c_obj{pn_file_input(f)} {}
    input_view(pn_input_t in) : _c_obj{in} {}

               operator bool() const { return c_obj()->type && !eof() && !error(); }
    input_view check();

    template <typename... arguments>
    input_view& read(arguments... args);

    bool error() const { return pn_input_error(c_obj()); }
    bool eof() const { return pn_input_eof(c_obj()); }

    pn_input_t*       c_obj() { return &_c_obj; }
    const pn_input_t* c_obj() const { return &_c_obj; }

  private:
    pn_input_t _c_obj;
};

[[clang::warn_unused_result]] bool parse(input_view in, value_ptr out, pn_error_t* error);

namespace internal {

template <typename tuple, int... i>
void apply_read(pn_input_t* in, const char* format, const tuple& args, indexes<i...>) {
    pn_read(in, format, std::get<i>(args)...);
}

template <typename tuple>
void read(pn_input_t* in, const char* format, const tuple& args) {
    apply_read(in, format, args, typename index_range<std::tuple_size<tuple>::value>::type());
}

}  // namespace internal

template <typename... args>
input& input::read(args... arg) {
    internal::read(
            c_obj(), (char[]){internal::read_arg<typename std::decay<args>::type>::code..., '\0'},
            std::tuple_cat(
                    internal::read_arg<typename std::decay<args>::type>::read_args(arg)...));
    return *this;
}

template <typename... args>
input_view& input_view::read(args... arg) {
    internal::read(
            c_obj(), (char[]){internal::read_arg<typename std::decay<args>::type>::code..., '\0'},
            std::tuple_cat(
                    internal::read_arg<typename std::decay<args>::type>::read_args(arg)...));
    return *this;
}

}  // namespace pn

#endif  // PN_INPUT_
