#include <math.h>
#include <string.h>
#include <iostream>

enum Kind {
  Val,
  Var,
  Add,
  Mul,
  Pow,
  Ln
};

class Expr {
public:
  Kind kind;
  Expr(Kind k) {
    this->kind = k;
  }
};

class ValExpr : public Expr {
public:
  long value;
  ValExpr(long i) : Expr(Val) {
    this->value = i;
  }
};

class VarExpr : public Expr {
public:
  const char* name;
  VarExpr( const char* n ) : Expr(Var) {
    this->name = n;
  }
};

class UnaryExpr : public Expr {
public:
  const Expr* expr;
  UnaryExpr( Kind k, const Expr* e ) : Expr(k) {
    this->expr = e;
  }
};

class BinExpr : public Expr {
public:
  const Expr* left;
  const Expr* right;
  BinExpr( Kind k, const Expr* e1, const Expr* e2 ) : Expr(k) {
    this->left = e1;
    this->right = e2;
  }

};

static long pown(long x, long n) {
  if (n==0) return 1;
  else if (n == 1) return x;
  else {
    long y = pown(x, n/2);
    return (y * y * (n%2 == 0 ? 1 : x));
  }
}

static const Expr* add( const Expr* x, const Expr* y ) {
  if (x->kind == Val && y->kind == Val) {
    return new ValExpr(((ValExpr*)x)->value + ((ValExpr*)y)->value);
  }
  else if (x->kind==Val && ((ValExpr*)x)->value==0) {
    return y;
  }
  else if (y->kind==Val && ((ValExpr*)y)->value==0) {
    return x;
  }
  else if (y->kind==Val) {
    return add(y,x);
  }
  else if (x->kind==Val && y->kind==Add && ((BinExpr*)y)->left->kind==Val) {
    long lval = ((ValExpr*)((BinExpr*)y)->left)->value;
    return add(new ValExpr(((ValExpr*)x)->value + lval), ((BinExpr*)y)->right);
  }
  else if (y->kind==Add && ((BinExpr*)y)->left->kind==Val) {
    return add(((BinExpr*)y)->left,add(x,((BinExpr*)y)->right));
  }
  else if (x->kind==Add) {
    return add(((BinExpr*)x)->left,add(((BinExpr*)x)->right,y));
  }
  else {
    return new BinExpr(Add,x,y);
  }
}

static const Expr* mul( const Expr* x, const Expr* y ) {
  if (x->kind == Val && y->kind == Val) {
    return new ValExpr(((ValExpr*)x)->value * ((ValExpr*)y)->value);
  }
  else if (x->kind==Val && ((ValExpr*)x)->value==0) {
    return x;
  }
  else if (y->kind==Val && ((ValExpr*)y)->value==0) {
    return y;
  }
  else if (x->kind==Val && ((ValExpr*)x)->value==1) {
    return y;
  }
  else if (y->kind==Val && ((ValExpr*)y)->value==1) {
    return x;
  }
  else if (y->kind==Val) {
    return mul(y,x);
  }
  else if (x->kind==Val && y->kind==Mul && ((BinExpr*)y)->left->kind==Val) {
    long lval = ((ValExpr*)((BinExpr*)y)->left)->value;
    return mul(new ValExpr(((ValExpr*)x)->value * lval), ((BinExpr*)y)->right);
  }
  else if (y->kind==Mul && ((BinExpr*)y)->left->kind==Val) {
    return mul(((BinExpr*)y)->left,mul(x,((BinExpr*)y)->right));
  }
  else if (x->kind==Mul) {
    return mul(((BinExpr*)x)->left,mul(((BinExpr*)x)->right,y));
  }
  else {
    return new BinExpr(Mul,x,y);
  }
}

static const Expr* powr( const Expr* x, const Expr* y) {
  if (x->kind == Val && y->kind == Val) {
    return new ValExpr( pown(((ValExpr*)x)->value,((ValExpr*)y)->value));
  }
  else if (y->kind==Val && ((ValExpr*)y)->value == 0) {
    return new ValExpr(1);
  }
  else if (y->kind==Val && ((ValExpr*)y)->value == 1) {
    return x;
  }
  else if (x->kind==Val && ((ValExpr*)x)->value == 0) {
    return new ValExpr(0);
  }
  else {
    return new BinExpr(Pow,x,y);
  }
}

static const Expr* ln(const Expr* n) {
  if (n->kind == Val && ((ValExpr*)n)->value == 1) {
    return new ValExpr(0);
  }
  else {
    return new UnaryExpr(Ln,n);
  }
}

static const Expr* d( const char* x, const Expr* e) {
  if (e->kind == Val) {
    return new ValExpr(0);
  }
  else if (e->kind==Var) {
    return new ValExpr( strcmp(((VarExpr*)e)->name,x)==0 ? 1 : 0);
  }
  else if (e->kind==Add) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return add(d(x,f),d(x,g));
  }
  else if (e->kind==Mul) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return add(mul(f,d(x,g)),mul(g,d(x,f)));
  }
  else if (e->kind==Pow) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return  mul(powr(f,g),add(mul(mul(g,d(x,f)),powr(f,new ValExpr(-1))),mul(ln(f),d(x,g))));
  }
  else { // if (e->kind==Ln) {
    const Expr* f = ((UnaryExpr*)e)->expr;
    return mul(d(x,f),powr(f,new ValExpr(-1)));
  }
}

static long count( const Expr* e) {
  if (e->kind == Val) {
    return 1;
  }
  else if (e->kind==Var) {
    return 1;
  }
  else if (e->kind==Add) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return count(f) + count(g);
  }
  else if (e->kind==Mul) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return count(f) + count(g);
  }
  else if (e->kind==Pow) {
    const Expr* f = ((BinExpr*)e)->left;
    const Expr* g = ((BinExpr*)e)->right;
    return count(f) + count(g);
  }
  else { // if (e->kind==Ln) {
    const Expr* f = ((UnaryExpr*)e)->expr;
    return count(f);
  }
}

static const Expr* deriv(long i, const Expr* e) {
  const Expr* f = d("x",e);
  std::cout << (i+1) << " count: " << count(f) << "\n";
  return f;
}

static const Expr* nest( long s, const Expr* e) {
  long n = s;
  while(n > 0) {
    e = deriv(s - n, e);
    n--;
  }
  return e;
}


int main(int argc, char ** argv) {
  unsigned n = 10;
  if (argc == 2) {
    n = atoi(argv[1]);
  }
  const Expr* x = new VarExpr("x");
  const Expr* e = powr(x,x);
  nest(n,e);
  std::cout << "done\n";
  return 0;
}
