#ifndef ds_set_h_
#define ds_set_h_

#include <iostream>
#include <utility>

// -------------------------------------------------------------------
// DS_SET CLASS -- WITH NESTED NODE & ITERATOR CLASSES (ALTERNATE STYLE)

template <class T>
class ds_set {
public:

  // -------------------------------------------------------------------
  // NODE CLASS 
  class Node {
  public:
    Node() : left(NULL), right(NULL), parent(NULL) {}
    Node(const T& init) : value(init), left(NULL), right(NULL), parent(NULL) {}
    T value;
    Node* left;
    Node* right;
    Node* parent; // to allow implementation of iterator increment & decrement
  };
  
  // -------------------------------------------------------------------
  // ITERATOR CLASS
  class iterator {
    public:
    iterator() : ptr_(NULL), dsset_(NULL) {}
    iterator(Node* p, const ds_set * s) : ptr_(p), dsset_(s) {}
    // operator* gives constant access to the value at the pointer
    const T& operator*() const { return ptr_->value; }
    // comparions operators are straightforward
    bool operator== (const iterator& rgt)
    { return (dsset_ == rgt.dsset_ && ptr_ == rgt.ptr_); }
    bool operator!= (const iterator& rgt)
    { return (dsset_ != rgt.dsset_ || ptr_ != rgt.ptr_); }
    // pre & post increment & decrement operators
    iterator & operator++();   // ++itr
    iterator operator++(int) { iterator temp(*this); ++(*this); return temp; }  // itr++
    iterator & operator--();   // --itr
    iterator operator--(int) { iterator temp(*this); --(*this); return temp; }  // itr--
  private:
    // representation
    Node* ptr_;
    const ds_set* dsset_;
  };

  // ------------------------------------------------------
  // DS_SET CONSTRUCTORS, ASSIGNMENT OPERATOR, & DESTRUCTOR
  ds_set() : root_(NULL), size_(0) {}
  ds_set(const ds_set& old) : size_(old.size_) { root_ = copy_tree(old.root_,NULL); }
  ~ds_set() { destroy_tree(root_); root_ = NULL; }
  ds_set& operator=(const ds_set<T>& old);

  // SET FUNCTIONALITY
  int size() const { return size_; }
  iterator begin() const;
  iterator end() const { return iterator(NULL,this); }
  iterator find(const T& key_value) { return find(key_value, root_); }
  std::pair< iterator, bool > insert(T const& key_value) { return insert(key_value, root_, NULL); }
  int erase(T const& key_value) { return erase(key_value, root_); }

  // PRINTING & DEBUGGING
  friend std::ostream& operator<< (std::ostream& ostr, const ds_set& s)
  { s.print_in_order(ostr, s.root_); return ostr; }
  void print_as_sideways_tree(std::ostream& ostr) const
  { print_as_sideways_tree(ostr, root_, 0); }
  bool sanity_check() const;

  // FOR LAB
  T accumulate(T arg) { return arg; }
  
private:
  // REPRESENTATION
  Node* root_;
  int size_;

  // PRIVATE HELPER FUNCTIONS
  Node* copy_tree(Node* old_root, Node* the_parent);
  void destroy_tree(Node* p);
  iterator find(const T& key_value, Node* p);
  std::pair<iterator,bool> insert(const T& key_value, Node*& p, Node* the_parent);
  int erase(T const& key_value, Node* &p);
  void print_in_order(std::ostream& ostr, const Node* p) const;
  void print_as_sideways_tree(std::ostream& ostr, const Node* p, int depth) const;
  bool sanity_check(Node* p) const;
};


// ===============================================================================
// DS_SET::ITERATOR FUNCTIONS

template <class T>
typename ds_set<T>::iterator& ds_set<T>::iterator::operator++() { 
  if (ptr_->right != NULL) { // find the leftmost child of the right node
    ptr_ = ptr_->right;
    while (ptr_->left != NULL) { ptr_ = ptr_->left; }
  } else { // go upwards along right branches...  stop after the first left
    while (ptr_->parent != NULL && ptr_->parent->right == ptr_) { ptr_ = ptr_->parent; }
    ptr_ = ptr_->parent;
  }
  return *this;
}

template <class T>
typename ds_set<T>::iterator& ds_set<T>::iterator::operator--() { 

}

// ===============================================================================
// DS_SET FUNCTIONS

template <class T>
ds_set<T>& ds_set<T>::operator=(const ds_set<T>& old) {
  if (&old != this) {
    destroy_tree(root_);
    root_ = copy_tree(old.root_,NULL);
    size_ = old.size_;
  }
  return *this;
}

template <class T>
typename ds_set<T>::Node* ds_set<T>::copy_tree(Node* old_root, Node* the_parent) {
  if (old_root == NULL)
    return NULL;
  Node *answer = new Node();
  answer->value = old_root->value;
  answer->left = copy_tree(old_root->left,answer);
  answer->right = copy_tree(old_root->right,answer);
  answer->parent = the_parent;
  return answer;
}

template <class T>
void ds_set<T>::destroy_tree(Node* p) {
  if (!p) return;
  destroy_tree(p->right);
  destroy_tree(p->left);
  delete p;
}

template <class T>
typename ds_set<T>::iterator ds_set<T>::begin() const { 
  if (!root_) return iterator(NULL,this);
  Node* p = root_;
  while (p->left) p = p->left;
  return iterator(p,this);
}

template <class T>
typename ds_set<T>::iterator ds_set<T>::find(const T& key_value, Node* p) {
  if (!p) return end();
  if (p->value > key_value)
    return find(key_value, p->left);
  else if (p->value < key_value)
    return find(key_value, p->right);
  else
    return iterator(p,this);
}

template <class T>
std::pair<typename ds_set<T>::iterator,bool>
ds_set<T>::insert(const T& key_value, Node*& p, Node* the_parent) {
  if (!p) {
    p = new Node(key_value);
    p->parent = the_parent;
    size_++;
    return std::pair<iterator,bool>(iterator(p,this), true);
  }
  else if (key_value < p->value)
    return insert(key_value, p->left, p);
  else if (key_value > p->value)
    return insert(key_value, p->right, p);
  else
    return std::pair<iterator,bool>(iterator(p,this), false);
}

template <class T>
int ds_set<T>::erase(T const& key_value, Node* &p) {
  if (!p) return 0;
  // look left & right
  if (p->value < key_value)
    return erase(key_value, p->right);
  else if (p->value > key_value)
    return erase(key_value, p->left);
  // Found the node.  Let's delete it
  assert (p->value == key_value);
  if (!p->left && !p->right) { // leaf
    delete p; 
    p=NULL;       
    size_--;    
  } else if (!p->left) { // no left child
    Node* q = p; 
    p=p->right; 
    assert (p->parent == q);
    p->parent = q->parent;
    delete q; 
    size_--;    
  } else if (!p->right) { // no right child
    Node* q = p; 
    p=p->left;
    assert (p->parent == q);
    p->parent = q->parent;
    delete q; 
    size_--;
  } else { // Find rightmost node in left subtree
    Node* q = p->left;
    while (q->right) q = q->right;
    p->value = q->value;
    // recursively remove the value from the left subtree
    int check = erase(q->value, p->left);
    assert (check == 1);
  }
  return 1;
}

template <class T>
void ds_set<T>::print_in_order(std::ostream& ostr, const Node* p) const {
  if (p) {
    print_in_order(ostr, p->left);
    ostr << p->value << "\n";
    print_in_order(ostr, p->right);
  }
}

template <class T>
void ds_set<T>::print_as_sideways_tree(std::ostream& ostr, const Node* p, int depth) const {
  if (p) {
    print_as_sideways_tree(ostr, p->right, depth+1);
    for (int i=0; i<depth; ++i) ostr << "    ";
    ostr << p->value << "\n";
    print_as_sideways_tree(ostr, p->left, depth+1);
  }
}

template <class T>
bool ds_set<T>::sanity_check() const {
  if (root_ == NULL) return true;
  if (root_->parent != NULL) {
    return false;
  }
  return sanity_check(root_);
}

template <class T>
bool ds_set<T>::sanity_check(Node* p) const {
  if (p == NULL) return true;
  if (p->left != NULL && p->left->parent != p) {
    std::cout << "Error: this node's left child's parent should be this node!" << std::endl;
    return false;
  }
  if (p->right != NULL && p->right->parent != p) {
    std::cout << "Error: this node's right child's parent should be this node!" << std::endl;
    return false;
  }
  return sanity_check(p->left) && sanity_check(p->right);
}

#endif
