#include <iostream>
#include <functional>
#include <sstream>
#include <map>
#include <vector>
#include <limits>
#include <memory>
#include <cmath>

class BaseBody;
typedef std::shared_ptr<BaseBody> BodyPtr;

class Object {
public:
  Object(int);
  Object(double);
  Object(const std::string&);
  Object(const char*);
  Object(char);
  Object(bool);
  template<typename R, typename... T> Object(R(*)(T...));
  template<typename R, typename... T> Object(std::function<R(T...)>);
  Object();
  Object(BaseBody*);
  Object(BodyPtr);
  Object(const Object&);
  
  ~Object();
  
  Object& operator = (const Object& o);
  Object toString() const;
  Object& operator [] (const Object& key);
  const Object& operator [] (const Object& key) const;
  
  Object operator ! () const;
  Object operator ~ () const;
  Object operator + () const;
  Object operator - () const;
  
  friend Object operator + (const Object&, const Object&);
  friend Object operator - (const Object&, const Object&);
  friend Object operator * (const Object&, const Object&);
  friend Object operator / (const Object&, const Object&);
  friend Object operator << (const Object&, const Object&);
  friend Object operator >> (const Object&, const Object&);
  friend Object operator && (const Object&, const Object&);
  friend Object operator || (const Object&, const Object&);
  friend Object operator & (const Object&, const Object&);
  friend Object operator | (const Object&, const Object&);
  friend Object operator ^ (const Object&, const Object&);
  
  Object& operator ++ () {
    return *this = *this + 1;
  }
  
  Object operator ++ (int) {
    Object o = *this;
    *this = *this + 1;
    return o;
  }
  
  Object& operator -- () {
    return *this = *this - 1;
  }
  
  Object operator -- (int) {
    Object o = *this;
    *this = *this - 1;
    return o;
  }

private:
  template <typename... T>
  Object call (std::vector<BodyPtr>& arguments, const Object& h, T... t) const;
  
  Object call (std::vector<BodyPtr>& arguments) const;
  
public:
  template <typename... T>
  Object operator () (T... t) const {
    std::vector<BodyPtr> v;
    return call(v, t...);
  }
  
  Object operator () () const {
    std::vector<BodyPtr> v;
    return call(v);
  }

  Object& operator +=  (const Object& rhs) { return *this = *this +  rhs; }
  Object& operator -=  (const Object& rhs) { return *this = *this -  rhs; }
  Object& operator *=  (const Object& rhs) { return *this = *this *  rhs; }
  Object& operator /=  (const Object& rhs) { return *this = *this /  rhs; }
  Object& operator <<= (const Object& rhs) { return *this = *this << rhs; }
  Object& operator >>= (const Object& rhs) { return *this = *this >> rhs; }
  Object& operator &=  (const Object& rhs) { return *this = *this &  rhs; }
  Object& operator |=  (const Object& rhs) { return *this = *this |  rhs; }
  Object& operator ^=  (const Object& rhs) { return *this = *this ^  rhs; }
  
  friend std::ostream& operator << (std::ostream& os, const Object& o);
  
  friend class Number;
  friend class String;
  friend class Boolean;
  friend class Math;
  
private:
  std::shared_ptr<BaseBody> value;
};

class ObjectBody;
class NumberBody;
class StringBody;
class BoolBody;
class NullBody;
class UndefinedBody;

class BaseBody {
public:
  BaseBody() : proto(new std::map<std::string, Object>) {}
  BaseBody(const BaseBody& o) : proto(o.proto) {}
  
  virtual ~BaseBody(){}
    
  virtual BaseBody* clone () const = 0;
  
  virtual Object rAdd (const BaseBody& lhs) const = 0;
  virtual Object add (const ObjectBody& rhs) const = 0;
  virtual Object add (const NumberBody& rhs) const = 0;
  virtual Object add (const StringBody& rhs) const = 0;
  virtual Object add (const BoolBody& rhs) const = 0;
  virtual Object add (const NullBody& rhs) const = 0;
  virtual Object add (const UndefinedBody& rhs) const = 0;
  
  virtual Object call (std::vector<BodyPtr>& arguments) const {
    std::cerr << "[CALLING OBJECT]";
    throw;
  }
  
  virtual Object& operator [] (const std::string& key) {
    return (*proto)[key];
  }
  
  virtual const Object& operator [] (const std::string& key) const {
    return (*proto)[key];
  }
  
  virtual const std::string toString() const = 0;
  virtual double toNumber() const = 0;
  virtual int toInt() const {
    double d = toNumber();
    if (std::isnan(d)) return 0;
    if (d > +9.9999999999998e24) return 0;
    if (d < -9.9999999999998e24) return 0;
    return d;
  }
  virtual bool toBool() const = 0;
private:
  std::shared_ptr<std::map<std::string, Object>> proto;
};

class ObjectBody : public BaseBody {
public:
  virtual BaseBody* clone () const {
    return new ObjectBody(*this);
  }
  
  virtual Object rAdd (const BaseBody& lhs) const;
  virtual Object add (const ObjectBody& rhs) const;
  virtual Object add (const NumberBody& rhs) const;
  virtual Object add (const StringBody& rhs) const;
  virtual Object add (const BoolBody& rhs) const;
  virtual Object add (const NullBody& rhs) const;
  virtual Object add (const UndefinedBody& rhs) const;
  
  virtual const std::string toString() const {
    return "[object Object]";
  }
  virtual double toNumber() const {
    return std::numeric_limits<double>::quiet_NaN();
  }
  virtual bool toBool() const {
    return true;
  }
};

class NullBody : public BaseBody {
public:
  virtual BaseBody* clone () const {
    return new NullBody(*this);
  }
  
  virtual Object rAdd (const BaseBody& lhs) const;
  virtual Object add (const ObjectBody& rhs) const;
  virtual Object add (const NumberBody& rhs) const;
  virtual Object add (const StringBody& rhs) const;
  virtual Object add (const BoolBody& rhs) const;
  virtual Object add (const NullBody& rhs) const;
  virtual Object add (const UndefinedBody& rhs) const;
  
  virtual const std::string toString() const {
    return "null";
  }
  virtual double toNumber() const {
    return 0;
  }
  virtual bool toBool() const {
    return false;
  }
};

class UndefinedBody : public NullBody {
public:
  virtual BaseBody* clone () const {
    return new UndefinedBody(*this);
  }
  
  static BodyPtr instance () {
    static BodyPtr instance;
    if(instance == NULL) instance.reset(new UndefinedBody);
    return instance;
  }

  virtual Object rAdd (const BaseBody& lhs) const;
  
  virtual const std::string toString() const {
    return "undefined";
  }
  virtual double toNumber() const {
    return std::numeric_limits<double>::quiet_NaN();
  }
  virtual bool toBool() const {
    return false;
  }
};

template <size_t size, size_t index, typename R, typename... T>
struct FunctionCall {
  template <typename... A>
  static Object call(std::function<R(T...)> func, std::vector<BodyPtr>& arguments, const A&... v) {
    BodyPtr arg = index < arguments.size() ? arguments[index] : UndefinedBody::instance();
    return FunctionCall<size, index+1, R, T...>::call(func, arguments, v..., Object(arg));
  }
};

template <size_t x, typename R, typename... T>
struct FunctionCall <x, x, R, T...> {
  template <typename... A>
  static Object call(std::function<R(T...)> func, std::vector<BodyPtr>& arguments, const A&... v) {
    return Object(func(v...));
  }
};

template <typename R, typename... T>
class FunctionBody : public ObjectBody {
public:
  FunctionBody(std::function<R(T...)> func) : value(func) {}
  FunctionBody(R(*func)(T...)) : value(func) {}

  virtual FunctionBody* clone () const {
    return new FunctionBody(*this);
  }

  virtual const std::string toString() const {
    return "function () { [naive code] }";
  }
  
  virtual Object call (std::vector<BodyPtr>& arguments) const {
    return FunctionCall<sizeof... (T), 0, R, T...>::call(value, arguments);
  }

private:
  std::function<R(T...)> value;
};

class NumberBody : public BaseBody {
public:
  explicit NumberBody(double v) : value(v) {}
  explicit NumberBody(int v) : value(v) {}
  
  virtual BaseBody* clone () const {
    return new NumberBody(*this);
  }
  
  virtual Object rAdd (const BaseBody& lhs) const;
  virtual Object add (const ObjectBody& rhs) const;
  virtual Object add (const NumberBody& rhs) const;
  virtual Object add (const StringBody& rhs) const;
  virtual Object add (const BoolBody& rhs) const;
  virtual Object add (const NullBody& rhs) const;
  virtual Object add (const UndefinedBody& rhs) const;
  
  virtual const std::string toString() const {
    if(std::isnan(value)) return "NaN";
    if(std::isinf(value)) return value < 0 ? "-Infinity" : "Infinity";
    
    std::stringstream s;
    s << value;
    return s.str();
  }
  virtual double toNumber() const {
    return value;
  }
  virtual bool toBool() const {
    if(std::isnan(value)) return false;
    return value;
  }
private:
  double value;
};

class StringBody : public BaseBody {
public:
  explicit StringBody(const std::string& s) : value(s) {}
  explicit StringBody(char c) : value() {
    value += c;
  }
  
  virtual BaseBody* clone () const {
    return new StringBody(*this);
  }
  
  virtual Object rAdd (const BaseBody& lhs) const;
  virtual Object add (const ObjectBody& rhs) const;
  virtual Object add (const NumberBody& rhs) const;
  virtual Object add (const StringBody& rhs) const;
  virtual Object add (const BoolBody& rhs) const;
  virtual Object add (const NullBody& rhs) const;
  virtual Object add (const UndefinedBody& rhs) const;
  
  virtual const std::string toString() const {
    return value;
  }
  virtual double toNumber() const {
    std::stringstream s(value);
    double v;
    s >> v;
    return v;
  }
  virtual bool toBool() const {
    return value.size();
  }
private:
  std::string value;
};

class BoolBody : public BaseBody {
public:
  explicit BoolBody(bool b) : value(b) {}
  
  virtual BaseBody* clone () const {
    return new BoolBody(*this);
  }
  
  virtual Object rAdd (const BaseBody& lhs) const;
  virtual Object add (const ObjectBody& rhs) const;
  virtual Object add (const NumberBody& rhs) const;
  virtual Object add (const StringBody& rhs) const;
  virtual Object add (const BoolBody& rhs) const;
  virtual Object add (const NullBody& rhs) const;
  virtual Object add (const UndefinedBody& rhs) const;
  
  virtual const std::string toString() const {
    return value ? "true" : "false";
  }
  virtual double toNumber() const {
    return value;
  }
  virtual bool toBool() const {
    return value;
  }
private:
  bool value;
};

Object ObjectBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}
Object ObjectBody::add (const ObjectBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object ObjectBody::add (const NumberBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object ObjectBody::add (const StringBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object ObjectBody::add (const BoolBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object ObjectBody::add (const NullBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object ObjectBody::add (const UndefinedBody& rhs) const {
  return Object(toString() + rhs.toString());
}

Object NumberBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}
Object NumberBody::add (const ObjectBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object NumberBody::add (const NumberBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NumberBody::add (const StringBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object NumberBody::add (const BoolBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NumberBody::add (const NullBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NumberBody::add (const UndefinedBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}

Object StringBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}
Object StringBody::add (const ObjectBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object StringBody::add (const NumberBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object StringBody::add (const StringBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object StringBody::add (const BoolBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object StringBody::add (const NullBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object StringBody::add (const UndefinedBody& rhs) const {
  return Object(toString() + rhs.toString());
}

Object BoolBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}
Object BoolBody::add (const ObjectBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object BoolBody::add (const NumberBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object BoolBody::add (const StringBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object BoolBody::add (const BoolBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object BoolBody::add (const NullBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object BoolBody::add (const UndefinedBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}

Object NullBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}
Object NullBody::add (const ObjectBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object NullBody::add (const NumberBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NullBody::add (const StringBody& rhs) const {
  return Object(toString() + rhs.toString());
}
Object NullBody::add (const BoolBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NullBody::add (const NullBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}
Object NullBody::add (const UndefinedBody& rhs) const {
  return Object(toNumber() + rhs.toNumber());
}

Object UndefinedBody::rAdd (const BaseBody& lhs) const {
  return lhs.add(*this);
}

Object::Object(int v) : value(new NumberBody(v)) {}
Object::Object(double v) : value(new NumberBody(v)) {}
Object::Object(const std::string& v) : value(new StringBody(v)) {}
Object::Object(const char* v) : value(new StringBody(v)) {}
Object::Object(char v) : value(new StringBody(v)) {}
template<typename R, typename... T>
Object::Object(R(*v)(T...)) : value(new FunctionBody<R, T...>(v)) {}
template<typename R, typename... T>
Object::Object(std::function<R(T...)> v) : value(new FunctionBody<R, T...>(v)) {}
Object::Object(bool v) : value(new BoolBody(v)) {}
Object::Object() : value(new ObjectBody) {}
Object::Object(BaseBody* o) : value(o) {}
Object::Object(BodyPtr o) : value(o) {}
Object::Object(const Object& o) : value(o.value) {}
Object::~Object() { }

Object& Object::operator = (const Object& o) {
  if(&o == this) return *this;
  value = o.value;
  return *this;
}

Object operator + (const Object& lhs, const Object& rhs) {
  return rhs.value->rAdd(*(lhs.value));
}

Object operator - (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toNumber() - rhs.value->toNumber());
}

Object operator * (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toNumber() * rhs.value->toNumber());
}

Object operator / (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toNumber() / rhs.value->toNumber());
}

Object operator << (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toInt() << rhs.value->toInt());
}
Object operator >> (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toInt() >> rhs.value->toInt());
}
Object operator && (const Object& lhs, const Object& rhs) {
  return lhs.value->toBool() ? rhs : lhs;
}
Object operator || (const Object& lhs, const Object& rhs) {
  return lhs.value->toBool() ? lhs : rhs;
}
Object operator & (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toInt() & rhs.value->toInt());
}
Object operator | (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toInt() | rhs.value->toInt());
}
Object operator ^ (const Object& lhs, const Object& rhs) {
  return Object(lhs.value->toInt() ^ rhs.value->toInt());
}

Object Object::toString() const {
  return Object(value->toString());
}

Object& Object::operator [] (const Object& key) {
  return (*value)[key.value->toString()];
}

const Object& Object::operator [] (const Object& key) const {
  return (*value)[key.value->toString()];
}

Object Object::operator ! () const {
  return Object(!value->toBool());
}

Object Object::operator ~ () const {
  return Object(~value->toInt());
}

Object Object::operator + () const {
  return Object(value->toNumber());
}

Object Object::operator - () const {
  return Object(-value->toNumber());
}

template <typename... T>
Object Object::call (std::vector<BodyPtr>& arguments, const Object& h, T... t) const {
  arguments.push_back(h.value);
  return call(arguments, t...);
}

Object Object::call (std::vector<BodyPtr>& arguments) const {
  return value->call(arguments);
}

std::ostream& operator << (std::ostream& os, const Object& o) {
  return os << o.value->toString();
}

static Object null(new NullBody);
static Object undefined(new UndefinedBody);
static Object NaN(std::numeric_limits<double>::quiet_NaN());
static Object Infinity(std::numeric_limits<double>::infinity());
static Object Number(static_cast<std::function<Object(Object)>>([](Object x) { return +x; }));
static Object String(static_cast<std::function<Object(Object)>>([](Object x) { return x + ""; }));
static Object Boolean(static_cast<std::function<Object(Object)>>([](Object x) { return !!x; }));

typedef Object var;

class ConsoleLogBody : public ObjectBody {
  virtual ConsoleLogBody* clone () const {
    return new ConsoleLogBody(*this);
  }

  virtual const std::string toString() const {
    return "function log() { [naive code] }";
  }
  
  virtual Object call (std::vector<BodyPtr>& arguments) const {
    for(size_t i = 0; i < arguments.size(); ++i) {
      if(i) std::cout << ' ';
      std::cout << arguments[i]->toString();
    }
    std::cout << std::endl;
    return undefined;
  }
};

class Console : public Object {
public:
  Console() : log(new ConsoleLogBody) {}
  Object log;
} console;

struct Math : public Object {
  Math() :
    sin(static_cast<std::function<Object(Object)>>([](Object x) { return std::sin(x.value->toNumber()); })),
    cos(static_cast<std::function<Object(Object)>>([](Object x) { return std::cos(x.value->toNumber()); })),
    PI(3.1415926) {}
  Object sin, cos, PI;
} Math;

struct Window : public Object {
  Window() : Math(Math), console(console) {}
  Object& Math, &console;
} window;

// TODO: как-то переписать unary, binary на Object(lambda_t)
template <typename F>
Object unary(F f) {
  return Object(static_cast<std::function<Object(Object)>>(f));
}

template <typename F>
Object binary(F f) {
  return Object(static_cast<std::function<Object(Object, Object)>>(f));
}

int main() {
  window["console"] = console;
  window["Math"] = Math;
  Math["sin"] = Math.sin;
  Math["cos"] = Math.cos;
  Math["PI"] = Math.PI;
  console["log"] = console.log;
  
  // EXAMPLE (Church numerals):
  
  var zero = unary([](var f){ return unary([f](var x){ return x; }); });
  var succ = unary([](var n){ return unary([n](var f){ return unary([n,f](var x){ return f(n(f)(x)); }); }); });
  var plus = unary([succ](var m){ return unary([m,succ](var n){ return m(succ)(n); }); });
  var mult = unary([](var m){ return unary([m](var n){ return unary([m,n](var f){ return m(n(f)); }); }); });
  var pred = unary([](var n){ return unary([n](var f){ return unary([n,f](var x){ return n (unary([f,x](var g){ return unary([f,g,x](var h){ return h (g (f)); }); })) (unary([x](var u){ return x; })) (unary([](var u){ return u; })); }); }); });
  
  var inc = unary([](var x) { return x+1; });
  var zeroValue = 0;
  
  console.log("0 is", zero(inc)(zeroValue));
  var two = succ(succ(zero));
  console.log("2 is", two(inc)(zeroValue));
  var four = plus(two)(two);
  console.log("4 is", four(inc)(zeroValue));
  var eight = mult(two)(four);
  console.log("8 is", eight(inc)(zeroValue));
  var seven = pred(eight);
  console.log("7 is", seven(inc)(zeroValue));
  
  console.log("7 is", unary([](var n){return unary([n](var f){return unary([n,f](var x){return n(unary([f,x](var g){return unary([f,g,x](var h){return h(g(f));});}))(unary([x](var u){return x;}))(unary([](var u){return u;}));});});})(unary([](var m){return unary([m](var n){return unary([m,n](var f){return m(n(f));});});})(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var f){return unary([f](var x){return x;});}))))(unary([](var m){return unary([m](var n){return m(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});}))(n);});})(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var f){return unary([f](var x){return x;});}))))(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var n){return unary([n](var f){return unary([n,f](var x){return f(n(f)(x));});});})(unary([](var f){return unary([f](var x){return x;});}))))))(unary([](var x){return x+1;}))(0));

  // EXAMPLE (JS):
  
  var x = 3;
  var y = x + null;
  var z = "hello, " + y;
  var n = NaN << NaN;
  var f = Object(static_cast<std::function<Object(Object, Object)>>([](Object x, Object y){ return x+y; }));
  
  console.log("x = " + x + " y = " + y + " z = " + z);
  console.log("x =", x,"y =", y, "z =", z);
  console.log(String("222") + true);
  console.log(String("222") + 3);
  console.log(Number("222") + 3);
  console.log(NaN << NaN, !NaN);
  console.log(undefined + 1);
  console.log(f("hello, ", "world"));
  console.log("sin(pi/4) = ", Math.sin(Math.PI / 4));
  console["log"]("sin(pi/4) = ", window["Math"]["sin"](Math["PI"] / 4));
}