#ifndef STAN_IO_JSON_JSON_PARSER_HPP
#define STAN_IO_JSON_JSON_PARSER_HPP

#include <boost/lexical_cast.hpp>

#include <stan/io/validate_zero_buf.hpp>
#include <stan/io/json/json_error.hpp>

#include <stdexcept>
#include <iostream>
#include <istream>
#include <sstream>
#include <string>

namespace stan {

  namespace json {

    const unsigned int MIN_HIGH_SURROGATE = 0xD800;
    const unsigned int MAX_HIGH_SURROGATE = 0xDBFF;
    const unsigned int MIN_LOW_SURROGATE = 0xDC00;
    const unsigned int MAX_LOW_SURROGATE = 0xDFFF;
    const unsigned int MIN_SUPPLEMENTARY_CODE_POINT = 0x010000;

    inline bool is_high_surrogate(unsigned int cp) {
      return (cp >= MIN_HIGH_SURROGATE && cp <= MAX_HIGH_SURROGATE);
    }

    inline bool is_low_surrogate(unsigned int cp) {
      return (cp >= MIN_LOW_SURROGATE && cp <= MAX_LOW_SURROGATE);
    }

    inline bool is_whitespace(char c) {
      return c == ' ' || c == '\n' || c == '\t' || c == '\r';
    }

    /**
     * A <code>json_parser</code> is a SAX-style streaming parser
     * that enforces JSON syntax and parses JSON elements
     * from an input stream, sending callbacks to a user-supplied
     * <code>json_handler</code>.
     */
    template <typename Handler, bool Validate_UTF_8>
    class parser {
    public:
      parser(Handler& h,
             std::istream& in)
        : h_(h),
          in_(in),
          next_char_(0),
          line_(0),
          column_(0)
      {  }

      ~parser() {
      }

      void parse() {
        h_.start_text();
        parse_text();
        h_.end_text();
      }

    private:
      json_error json_exception(const std::string& msg) const {
        std::stringstream ss;
        ss << "Error in JSON parsing at"
           << " line=" << line_ << " column=" << column_
           << std::endl
           << msg
           << std::endl;
        return json_error(ss.str());
      }

      // JSON-text = object / array
      void parse_text() {
        char c = get_non_ws_char();
        if (c == '{') {              // begin-object
          h_.start_object();
          parse_object_members_end_object();
          h_.end_object();
        } else if (c == '[') {      // begin-array
          // array
          h_.start_array();
          parse_array_values_end_array();
          h_.end_array();
        } else {
          throw json_exception("expecting start of object ({) or array ([)");
        }
      }

      // value =  false / null / true / object / array / number / string
      void parse_value() {
        // value
        char c = get_non_ws_char();
        if (c == 'f') {
          // false
          parse_false_literal();
        } else if (c == 'n') {
          // null
          parse_null_literal();
        } else if (c == 't') {
          // true
          parse_true_literal();
        } else if (c == '"') {
          // string
          h_.string(parse_string_chars_quotation_mark());
        } else if (c == '{' || c == '[') {
          // object / array
          unget_char();
          parse_text();
        } else if (c == '-' ||
                   (c >= '0' && c <= '9') ) {
          unget_char();
          parse_number();
        } else {
          throw json_exception("illegal value, expecting object, array, "
                               "number, string, or literal true/false/null");
        }
      }

      void parse_number() {
        bool is_positive = true;

        std::stringstream ss;
        char c = get_non_ws_char();
        // minus
        if (c == '-') {
          is_positive = false;
          ss << c;
          c = get_char();
        }

        // int
        //   zero / digit1-9
        if (c < '0' || c > '9')
          throw json_exception("expecting int part of number");
        ss << c;

        //   *DIGIT
        bool leading_zero = (c == '0');
        c = get_char();
        if (leading_zero && (c == '0'))
          throw json_exception("zero padded numbers not allowed");
        while (c >= '0' && c <= '9') {
          ss << c;
          c = get_char();
        }

        // frac
        bool is_integer = true;
        if (c == '.') {
          is_integer = false;
          ss << '.';
          c = get_char();
          if (c < '0' || c > '9')
            throw json_exception("expected digit after decimal");
          ss << c;
          c = get_char();
          while (c >= '0' && c <= '9') {
            ss << c;
            c = get_char();
          }
        }

        // exp
        if (c == 'e' || c == 'E') {
          is_integer = false;
          ss << c;
          c = get_char();
          // minus / plus
          if (c == '+' || c == '-') {
            ss << c;
            c = get_char();
          }
          // 1*DIGIT
          if (c < '0' || c > '9')
            throw json_exception("expected digit after e/E");
          while (c >= '0' && c <= '9') {
            ss << c;
            c = get_char();
          }
        }
        unget_char();

        if (is_integer) {
          if (is_positive) {
            unsigned long n;  // NOLINT(runtime/int)
            try {
              // NOLINTNEXTLINE(runtime/int)
              n = boost::lexical_cast<unsigned long>(ss.str());
            } catch (const boost::bad_lexical_cast & ) {
              throw json_exception("number exceeds integer range");
            }
            ss >> n;
            h_.number_unsigned_long(n);
          } else {
            long n;  // NOLINT(runtime/int)
            try {
              // NOLINTNEXTLINE(runtime/int)
              n = boost::lexical_cast<unsigned long>(ss.str());
            } catch (const boost::bad_lexical_cast & ) {
              throw json_exception("number exceeds integer range");
            }
            ss >> n;
            h_.number_long(n);
          }
        } else {
          double x;
          try {
            std::string ss_str = ss.str();
            x = boost::lexical_cast<double>(ss_str);
            if (x == 0)
              io::validate_zero_buf(ss_str);
          } catch (const boost::bad_lexical_cast & ) {
            throw json_exception("number exceeds double range");
          }
          ss >> x;
          h_.number_double(x);
        }
      }

      std::string parse_string_chars_quotation_mark() {
        std::stringstream s;
        while (true) {
          char c = get_char();
          if (c == '"') {
            return s.str();
          } else if (c == '\\') {
            c = get_char();
            if (c == '\\'  || c == '/' || c == '"') {
              s << c;
            } else if (c == 'b') {
              s << '\b';
            } else if (c == 'f') {
              s << '\f';
            } else if (c == 'n') {
              s << '\n';
            } else if (c == 'r') {
              s << '\r';
            } else if (c == 't') {
              s << '\t';
            } else if (c == 'u') {
              get_escaped_unicode(s);
            } else {
              throw json_exception("expecting legal escape");
            }
            continue;
          } else if (c > 0 && c < 0x20) {  // ASCII control characters
            throw json_exception("found control character, char values less "
                                 "than U+0020 must be \\u escaped");
          }
          s << c;
        }
      }

      void parse_true_literal() {
        get_chars("rue");
        h_.boolean(true);
      }

      void parse_false_literal() {
        get_chars("alse");
        h_.boolean(false);
      }

      void parse_null_literal() {
        get_chars("ull");
        h_.null();
      }

      void get_escaped_unicode(std::stringstream& s) {
        unsigned int codepoint = get_int_as_hex_chars();
        if (!(is_high_surrogate(codepoint) || is_low_surrogate(codepoint))) {
          putCodepoint(s, codepoint);
        } else if (!is_high_surrogate(codepoint)) {
          throw json_exception("illegal unicode values, found "
                               "low-surrogate, missing high-surrogate");
        } else {
          char c = get_char();
          if (!(c == '\\'))
            throw json_exception("illegal unicode values, found "
                                 "high-surrogate, expecting low-surrogate");
          c = get_char();
          if (!(c == 'u'))
            throw json_exception("illegal unicode values, found "
                                 "high-surrogate, expecting low-surrogate");
          unsigned int codepoint2 = get_int_as_hex_chars();
          unsigned int supplemental
            = ((codepoint - MIN_HIGH_SURROGATE) << 10)
            + (codepoint2 - MIN_LOW_SURROGATE)
            + MIN_SUPPLEMENTARY_CODE_POINT;
          putCodepoint(s, supplemental);
        }
      }

      unsigned int get_int_as_hex_chars() {
        std::stringstream s;
        s << std::hex;
        for (int i = 0; i < 4; i++) {
          char c = get_char();
          if (!((c >= 'a' && c<= 'f')
                || (c >= 'A' && c<= 'F')
                || (c >= '0' && c<= '9')))
            throw json_exception("illegal unicode code point");
          s << c;
        }
        unsigned int hex;
        s >> hex;
        return hex;
      }

      void putCodepoint(std::stringstream& s, unsigned int codepoint) {
        if (codepoint <= 0x7f) {
          s.put(codepoint);
        } else if (codepoint <= 0x7ff) {
          s.put(0xc0 | ((codepoint >> 6) & 0x1f));
          s.put(0x80 | (codepoint & 0x3f));
        } else if (codepoint <= 0xffff) {
          s.put(0xe0 | ((codepoint >> 12) & 0x0f));
          s.put(0x80 | ((codepoint >> 6) & 0x3f));
          s.put(0x80 | (codepoint & 0x3f));
        } else {
          s.put(0xf0 | ((codepoint >> 18) & 0x07));
          s.put(0x80 | ((codepoint >> 12) & 0x3f));
          s.put(0x80 | ((codepoint >> 6) & 0x3f));
          s.put(0x80 | (codepoint & 0x3f));
        }
      }

      void get_chars(const std::string& s) {
        for (size_t i = 0; i < s.size(); ++i) {
          char c = get_char();
          if (c != s[i])
            throw json_exception("expecting rest of literal: "
                                 + s.substr(i));
        }
      }

      void parse_array_values_end_array() {
        char c = get_non_ws_char();
        if (c == ']') return;
        unget_char();
        while (true) {
          parse_value();
          char c = get_non_ws_char();
          if (c == ']') return;
          if (c != ',') {
            throw json_exception("in array, expecting ] or ,");
          }
          c = get_non_ws_char();
          if (c == ']')
            throw json_exception("in array, expecting value");
          unget_char();
        }
      }

      void parse_object_members_end_object() {
        char c = get_non_ws_char();
        if (c == '}') return;
        while (true) {
          // string (key)
          if (c != '"')
            throw json_exception("expecting member key"
                                 " or end of object marker (})");
          std::string key = parse_string_chars_quotation_mark();
          h_.key(key);
          // name-separator separator
          c = get_non_ws_char();
          if (c != ':')
            throw json_exception("expecting key-value separator :");
          // value
          parse_value();

          // continuation
          c = get_non_ws_char();
          if (c == '}')
            return;
          if (c != ',')
            throw json_exception("expecting end of object } or separator ,");
          c = get_non_ws_char();
        }
      }

      char get_char() {
        char c = in_.get();
        if (!in_.good())
          throw json_exception("unexpected end of stream");
        if (c == '\n') {
          ++line_;
          column_ = 1;
        } else {
          ++column_;
        }
        return c;
      }

      char get_non_ws_char() {
        while (true) {
          char c = get_char();
          if (is_whitespace(c)) continue;
          return c;
        }
      }

      void unget_char() {
        in_.unget();
        --column_;
      }

      Handler& h_;
      std::istream& in_;
      char next_char_;
      size_t line_;
      size_t column_;
    };


    /**
     * Parse the JSON text represented by the specified input stream,
     * sending events to the specified handler, and optionally
     * validating the UTF-8 encoding.
     *
     * @tparam Validate_UTF_8
     * @tparam Handler
     * @param in Input stream from which to parse
     * @param handler Handler for events from parser
     */
    template <bool Validate_UTF_8, typename Handler>
    void parse(std::istream& in,
               Handler& handler) {
      parser<Handler, Validate_UTF_8>(handler, in).parse();
    }

    /**
     * Parse the JSON text represented by the specified input stream,
     * sending events to the specified handler, and optionally
     * validating the UTF-8 encoding.
     *
     * @tparam Handler
     * @param in Input stream from which to parse
     * @param handler Handler for events from parser
     */
    template <typename Handler>
    void parse(std::istream& in,
               Handler& handler) {
      parse<false>(in, handler);
    }

  }
}
#endif
