/*! \file asdl.hxx
 *
 * \author John Reppy
 */

/*
 * COPYRIGHT (c) 2018 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 */

#ifndef _ASDL_HXX_
#define _ASDL_HXX_

#include "config.h"

#include <string>
#include <vector>
#include <istream>
#include <ostream>
#include <memory>

namespace asdl {

  //! forward declarations of integer and identifier classes
    class integer;
    class identifier;

  //! exception for decoding error
    class decode_exception {
    };

  //! ASDL output stream
    class outstream {
      public:
	explicit outstream (std::ostream *os) : _os(os) { }

      // no copying allowed!
	outstream (outstream const &) = delete;
	outstream &operator= (outstream const &) = delete;

      // move operations
	outstream (outstream &&os) noexcept
	{
	    this->_os = os._os;
	    os._os = nullptr;
	}
	outstream &operator= (outstream &&rhs) noexcept
	{
	    if (this != &rhs) {
		this->_os = rhs._os;
		rhs._os = nullptr;
	    }
	    return *this;
	}

	~outstream ()
	{
	    if (this->_os != nullptr) {
		delete this->_os;
	    }
	}

	void putc (char c) { this->_os->put(c); }
	void putb (unsigned char c) { this->_os->put(c); }

      protected:
	std::ostream *_os;
    };

  //! ASDL file outstream
    class file_outstream : public outstream {
      public:
	explicit file_outstream (std::string const &file);

      // no copying allowed!
	file_outstream (file_outstream const &) = delete;
	file_outstream &operator= (file_outstream const &) = delete;

	void close () { this->_os->flush(); }
    };

  //! ASDL memory outstream
    class memory_outstream : public outstream {
      public:
	explicit memory_outstream ();

      // no copying allowed!
	memory_outstream (memory_outstream const &) = delete;
	memory_outstream &operator= (memory_outstream const &) = delete;

	std::string get_pickle () const;
    };

  //! ASDL input stream
    class instream {
      public:
	explicit instream (std::istream *is) : _is(is) { }

      // no copying allowed!
	instream (instream const &) = delete;
	instream &operator= (instream const &) = delete;

      // move operations
	instream (instream &&is) noexcept
	{
	    this->_is = is._is;
	    is._is = nullptr;
	}
	instream &operator= (instream &&rhs) noexcept
	{
	    if (this != &rhs) {
		this->_is = rhs._is;
		rhs._is = nullptr;
	    }
	    return *this;
	}

	~instream ()
	{
	    if (this->_is != nullptr) {
		delete this->_is;
	    }
	}

	char getc () { return this->_is->get(); }
	unsigned char getb () { return static_cast<unsigned char>(this->_is->get()); }

      protected:
	std::istream *_is;
    };

  //! ASDL file instream
    class file_instream : public instream {
      public:
	explicit file_instream (std::string const &file);

      // no copying allowed!
	file_instream (file_instream const &) = delete;
	file_instream &operator= (file_instream const &) = delete;
    };

  //! ASDL memory outstream
    class memory_instream : public instream {
      public:
	explicit memory_instream (std::string const &data);

      // no copying allowed!
	memory_instream (memory_instream const &) = delete;
	memory_instream &operator= (memory_instream const &) = delete;
    };

  //! wrapper for immediate values
    template <typename T>
    class box {
      public:
	box (T v) : _v(v) { }
	box (instream &is);
	~box () { }
	T value () const { return this->_v; }

      private:
	T _v;
    };

  //! optional enum values
    template <typename T>
    class option {
      public:
	option () : _v(0) { }
	option (T v) : _v(static_cast<unsigned int>(v)) { }
	~option () { }
	bool isEmpty () const { return (this->_v == 0); }
	T valOf () const { return static_cast<T>(this->_v); }
      private:
	unsigned int _v;
    };

  /***** functions *****/

  // encode basic values
    void write_int (outstream & os, int i);
    void write_uint (outstream & os, unsigned int ui);
    inline void write_tag8 (outstream & os, unsigned int ui)
    {
	os.putb(static_cast<unsigned char>(ui));
    }
    inline void write_bool (outstream & os, bool b)
    {
	if (b) { write_tag8(os, 2); } else { write_tag8(os, 1); }
    }
    inline void write_bool_option (outstream & os, option<bool> & optB)
    {
	if (optB.isEmpty()) {
	    write_tag8 (os, 0);
	} else {
	    write_bool (os, optB.valOf());
	}
    }
    inline void write_tag (outstream & os, unsigned int ui)
    {
	write_uint(os, ui);
    }
    void write_string (outstream & os, std::string const & s);
    void write_integer (outstream & os, integer const & i);
  // generic pickler for boxed options
    template <typename T>
    inline void write_option (outstream & os, T *v)
    {
	if (v == nullptr) {
	    os.putb (0);
	} else {
	    os.putb (1);
	    v->write (os);
	}
    }
  // generic pickler for enumeration sequences with fewer than 256 constructors
    template <typename T>
    inline void write_small_enum_seq (outstream & os, std::vector<T> & seq)
    {
	write_uint (os, seq.size());
	for (auto it = seq.cbegin(); it != seq.cend(); ++it) {
	    write_tag8 (os, static_cast<unsigned int>(*it));
	}
    }
  // generic pickler for enumeration sequences with more than 256 constructors
    template <typename T>
    inline void write_big_enum_seq (outstream & os, std::vector<T> & seq)
    {
	write_uint (os, seq.size());
	for (auto it = seq.cbegin(); it != seq.cend(); ++it) {
	    write_uint (os, static_cast<unsigned int>(*it));
	}
    }
  // generic pickler for boxed sequences
    template <typename T>
    inline void write_seq (outstream & os, std::vector<T> & seq)
    {
	write_uint (os, seq.size());
	for (auto it = seq.cbegin(); it != seq.cend(); ++it) {
	    (*it)->write (os);
	}
    }

  // decode basic values
    int read_int (instream & is);
    unsigned int read_uint (instream & is);
    inline unsigned int read_tag8 (instream & is)
    {
	return is.getb();
    }
    inline bool read_bool (instream & is)
    {
	return (read_tag8(is) != 1);
    }
    inline option<bool> read_bool_option (instream & is)
    {
	unsigned int v = read_tag8(is);
	if (v == 0) {
	    return option<bool>();
	} else {
	    return option<bool>(v != 1);
	}
    }
    inline unsigned int read_tag (instream & is)
    {
	return read_uint (is);
    }
    std::string read_string (instream & is);
    integer read_integer (instream & is);
    template <typename T>
  // generic unpickler for enumeration sequences with more than 256 constructors
    inline T *read_option (instream & is)
    {
	unsigned int tag = is.getb ();
	if (tag == 0) {
	    return nullptr;
	} else {
	    return T::read (is);
	}
    }
  // generic pickler for enumeration sequences with fewer than 256 constructors
    template <typename T>
    inline std::vector<T> read_small_enum_seq (instream & is)
    {
	unsigned int len = read_uint (is);
	std::vector<T> seq;
	seq.reserve(len);
	for (unsigned int i = 0;  i < len;  ++i) {
	    seq.push_back (static_cast<T>(read_tag8(is)));
	}
	return seq;
    }
  // generic pickler for enumeration sequences with more than 256 constructors
    template <typename T>
    inline std::vector<T> read_big_enum_seq (instream & is)
    {
	unsigned int len = read_uint (is);
	std::vector<T> seq;
	seq.reserve(len);
	for (unsigned int i = 0;  i < len;  ++i) {
	    seq.push_back (static_cast<T>(read_uint(is)));
	}
	return seq;
    }
  // generic unpickler for boxed sequences
    template <typename T>
    inline std::vector<T *> read_seq (instream & is)
    {
	unsigned int len = read_uint (is);
	std::vector<T *> seq;
	seq.reserve(len);
	for (unsigned int i = 0;  i < len;  ++i) {
	    seq.push_back (T::read(is));
	}
	return seq;
    }

} // namespace asdl

#include "asdl-integer.hxx"
//#include "asdl-identifier.hxx"

#endif /* !_ASDL_HXX_ */
