#pragma once


#include <map>
#include <string>
#include <vector>
#include <sstream>
#include <exception>

const std::string VERSION = "ml 0.2 (" __DATE__ " " __TIME__ ")";

const std::string STDLIB_LOADER =
	 R"(
        (do (define ___lib_path "/usr/local/var/mlisp")
		(if (is-dir? ___lib_path)
		    (for file (ls-dir ___lib_path)
				(if (string-regex? file "^stdlib\.lsp$")
				    (include (+ ___lib_path "/" file)))
					)))
       )";


// Forward declaration for MlEnvironment class definition
class MlValue;


// An instance of a function's scope.
class MlEnvironment {
public:
    // Default constructor
    MlEnvironment() : parent_scope(nullptr) {}

    // Does this environment, or its parent environment,
    // have this atom in scope?
    // This is only used to determine which atoms to capture when
    // creating a lambda function.
    bool has(const std::string &name) const;

    // Get the value associated with this name in this scope
    MlValue get(const std::string &name) const;

    // Set the value associated with this name in this scope
    void set(const std::string &name, MlValue value);

    // Set the value associated with this name in this scope and parent scopes
    // and if not exists sets in this scope
    void setX(const std::string &name, MlValue value);

    // Get vector of executables in this scope
    std::vector<std::string> get_lambdas_list() const;

    void combine(MlEnvironment const &other);

    void set_parent_scope(MlEnvironment *parent) {
	    parent_scope = parent;
    }

    // Output this scope in readable form to a stream.
    friend std::ostream &operator<<(std::ostream &os, MlEnvironment const &v);

private:

    // The definitions in the scope.
    std::map<std::string, MlValue> defs;
    MlEnvironment *parent_scope;
};


// An exception thrown by the lisp
class MlError : public std::exception {
public:
    MlError() = delete;

    // Create an error with the value that caused the error,
    // the scope where the error was found, and the message.
    MlError(const MlValue &v, MlEnvironment const &env, const char *msg);

    // Copy constructor is needed to prevent double frees
    MlError(MlError const &other);

    ~MlError();

    // Get the printable error description.
    std::string description() const;

private:
    MlValue *cause;
    MlEnvironment env;
    const char *msg;
};


// The type for a builtin function, which takes a list of values,
// and the environment to run the function in.
typedef MlValue (*Builtin)(std::vector<MlValue>, MlEnvironment &);


class MlValue {
public:

    // Constructs a nil value
    MlValue();

    // Constructs an integer
    MlValue(long i);

    // Constructs a floating point value
    MlValue(double f);

    // Constructs a bool value
    MlValue(bool b);

    // Constructs a list
    MlValue(const std::vector<MlValue> &list);

    // Construct a quoted value
    static MlValue quote(const MlValue &quoted);

    // Construct an atom
    static MlValue atom(const std::string &s);

    // Construct a string
    static MlValue string(const std::string &s);

    // Construct a nil
    static MlValue nil();

    // Construct a lambda function
    MlValue(const std::vector<MlValue> &params, MlValue ret, MlEnvironment const &env);

    // Construct a builtin function
    MlValue(const std::string &name, Builtin b);

    std::vector<std::string> get_used_atoms();

    // Apply this as a function to a list of arguments in a given environment.
    MlValue apply(std::vector<MlValue> args, MlEnvironment &env);

    // Evaluate this value as lisp code.
    MlValue eval(MlEnvironment &env);

    bool is_builtin() const;

    bool is_number() const;

    bool is_string() const;

    // Get the boolean value of this value.
    bool as_bool() const;

    // Get this item's integer value
    long as_int() const;

    // Get this item's floating point value
    double as_float() const;

    // Get this item's string value
    std::string as_string() const;

    // Get this item's atom value
    std::string as_atom() const;

    // Get this item's list value
    std::vector<MlValue> as_list() const;

    // Push an item to the end of this list
    void push(MlValue val);

    // Push an item from the end of this list
    MlValue pop();


    // Cast this to an integer value
    MlValue cast_to_int() const;

    // Cast this to a floating point value
    MlValue cast_to_float() const;

    // Cast this to a string
    MlValue cast_to_string() const;


    bool operator==(MlValue other) const;

    bool operator!=(const MlValue &other) const;


    bool operator>=(const MlValue &other) const;

    bool operator<=(const MlValue &other) const;

    bool operator>(const MlValue &other) const;

    bool operator<(const MlValue &other) const;


    // This function adds two lisp values, and returns the lisp value result.
    MlValue operator+(const MlValue &other) const;

    // This function subtracts two lisp values, and returns the lisp value result.
    MlValue operator-(const MlValue &other) const;

    // This function multiplies two lisp values, and returns the lisp value result.
    MlValue operator*(const MlValue &other) const;

    // This function divides two lisp values, and returns the lisp value result.
    MlValue operator/(const MlValue &other) const;

    // This function finds the remainder of two lisp values, and returns the lisp value result.
    MlValue operator%(const MlValue &other) const;

    // Get the name of the type of this value
    std::string get_type_name() const;

    std::string display() const;

    std::string debug() const;

    friend std::ostream &operator<<(std::ostream &os, MlValue const &v);

private:
    enum {
	QUOTE,
	ATOM,
	INT,
	FLOAT,
	LIST,
	STRING,
	LAMBDA,
	BUILTIN,
	NIL
    } type;

    union {
	long i;
	double f;
	Builtin b;
    } stack_data;

    std::string str;
    std::vector<MlValue> list;
    MlEnvironment lambda_scope;
};
