#include <string>
#include <stack>
#include <vector>
#include <list>
#include <iostream>
#include <memory>
#include <cassert>

using namespace std;

// -------------------------------------------------------------*
//  *   ZAD1 obserwator z weak_ptr
//  *--------------------------------------------------------------
class Observer {
 public:
    virtual void update() = 0;
    virtual ~Observer(){}
};

class Subject {
public:
    Subject() { cout << "c-tor Subject" << endl; }
    typedef std::vector<weak_ptr<Observer>> Observers;

    void add(weak_ptr<Observer> o){ obs_.push_back(o); }
    void notify() {
        for(weak_ptr<Observer> o : obs_)
            if(! o.expired() )
                o.lock()->update();
    }
    virtual ~Subject(){ cout << "d-tor Subject" << endl; }
private:
    Observers obs_;
};

class ConcreteObs : public Observer, public std::enable_shared_from_this<ConcreteObs> {
public:
    //funkcja fabryczna
    static shared_ptr<ConcreteObs> create(shared_ptr<Subject> sub) {
        shared_ptr<ConcreteObs> ptr(new ConcreteObs(sub));
        ptr->init();
    }

    ConcreteObs(shared_ptr<Subject> sub) : sub_(sub) { cout << "c-tor ConcreteObs" << endl; }
    void init() {
        me_ = shared_from_this();
        sub_->add(me_);
    }
    virtual ~ConcreteObs() { cout << "d-tor ConcreteObs" << endl; }

    virtual void update() { cout << "update" << endl; }
private:
    shared_ptr<Subject> sub_;
    weak_ptr<ConcreteObs> me_;
};


void zad1() {
    shared_ptr<Subject> subject(new Subject);
    shared_ptr<ConcreteObs> obs1 = ConcreteObs::create(subject);
    shared_ptr<ConcreteObs> obs2 = ConcreteObs::create(subject);
    obs1.reset();

    subject->notify();
}

// ---------------------------------------------------------------------------------*
//  * ZAD2                                 *
//  * problem: sterowanie przy obsłudze wyjątków, sprytne wskazniki, r-value reference *
//  *----------------------------------------------------------------------------------

class E : public std::exception {
public:
  E(int i) : i_(i) {}
  int i_;
};

struct F {
  F(int i) : i_(i) { }
  virtual ~F() { cout << i_ << ' '; }
  int i_;
};

typedef unique_ptr<F> PF;

void f(PF&& pf) {
  cout << "f PF&&" << endl;
  if(pf->i_ > 2)
    f( PF(new F(pf->i_ - 2)));
  else
    throw E(pf->i_);
}

void f(const PF& pf) {
   cout << "f const PF&" << endl;
  if( pf->i_ > 1)
    f( PF(new F(pf->i_ / 2)));
}


int LICZBA_LITER_NAZWISKA = 5;

void zad2() {
  for(int i=3;i<15;++i) {
    LICZBA_LITER_NAZWISKA = i;
    cout << LICZBA_LITER_NAZWISKA << ": ";
    try {
      PF pb(new F(LICZBA_LITER_NAZWISKA - 1));
      f(pb);
    } catch(E& e){
      cout << e.i_;
    }
    cout << endl;
  }
}
// ---------------------------------------------------------------------------------*
//  * ZAD3 - uzycie wizytatora                                 *
//  *----------------------------------------------------------------------------------

class Visitor;

class BaseException : public std::exception {
public:
  virtual void accept(Visitor& v)  = 0;
};

class NetworkError;
class UserBreak;
class BadData;

class Visitor {
public:
  virtual ~Visitor() {}
  virtual void visit(NetworkError&) = 0;
  virtual void visit(UserBreak&) = 0;
  virtual void visit(BadData&) = 0;
};

class NetworkError : public BaseException {
public:
  virtual void accept(Visitor& v) { v.visit(*this); }
};

class UserBreak : public BaseException {
public:
  virtual void accept(Visitor& v) { v.visit(*this); }
};

class BadData : public BaseException {
public:
  virtual void accept(Visitor& v) { v.visit(*this); }
};

class MsgGenerator : public Visitor {
public:
    MsgGenerator() = default;
    MsgGenerator(const MsgGenerator&) = default;
    MsgGenerator& operator=(const MsgGenerator&) = default;

    virtual void visit(NetworkError&) { msg_ = "Network Error"; }
    virtual void visit(UserBreak&) { msg_ = "User Break"; }
    virtual void visit(BadData&) { msg_ = "Bad Data"; }
    string msg_;
};

void zad3() {
  try {
    throw UserBreak();
    //kod ktory moze zglosic wyjatek z hierarchii BaseException
  } catch(NetworkError&) {
      cerr << "Network Error" << endl;
  } catch(UserBreak&) {
	  cerr << "User Break" << endl;
  } catch(BadData&) {
      cerr << "Bad Data" << endl;
  }
  // } catch(BaseException& e) {
  //     MsgGenerator gen;
  //     e.accept(gen);
  //     cerr << gen.msg_ << endl;
  // }
}


// ---------------------------------------------------------------------------------*
//  * ZAD4 - copy on write                                 *
//  *----------------------------------------------------------------------------------

struct Foo {
    Foo(int v) : val_(v), counter_(1) { cout << "c-tor Foo(" << val_ << ")" << endl; }
    ~Foo() { cout << "d-tor Foo(" << val_ << ")" << endl; }
    int val_;
    int counter_;
};

class CFoo {
public:
    CFoo(int v) : foo_(new Foo(v) ) {}

    CFoo(const CFoo& c) {
        join(c.foo_);
    }
    CFoo& operator=(const CFoo& c) {
        if(foo_ == c.foo_) return *this;
        unjoin();
        join(c.foo_);
    }
    CFoo(CFoo&& c) {
        join(c.foo_);
    }
    CFoo& operator=(CFoo&& c) {
        if(foo_ == c.foo_) return *this;
        unjoin();
        join(c.foo_);
    }
    ~CFoo() {
        unjoin();
    }

    void set(int v) {
        if(foo_->counter_ == 1) {
            foo_->val_ = v;
        }
        else {
            --foo_->counter_;
            foo_ = new Foo(v);
        }
    }
    int get() const { return foo_->val_; }
private:
    void join(Foo* f) {
        foo_ = f;
        ++foo_->counter_;
    }
    void unjoin() {
        --foo_->counter_;
        if(foo_->counter_ == 0)
            delete foo_;
    }
    Foo* foo_;
};

void zad4() {
    CFoo f1(1);
    CFoo f2(f1);
    f2 = f1;
    CFoo f3(2);
    f3 = f1;
    f2.set(2);
    f1.set(3);
}



// -------------------------------------------------------------*
// ZAD5 - dekorator
//  *--------------------------------------------------------------
class Tank {
public:
    virtual void fire()  = 0;
};

typedef shared_ptr<Tank> PTank;

class NormalTank : public Tank {
    virtual void fire() { cout << "fire" << flush; }
};

class Decorator : public Tank {
public:
    Decorator(PTank obj) : obj_(obj) {}
    virtual void fire() { obj_->fire(); }
private:
    PTank obj_;
};

class SuperOptics : public Decorator {
public:
    SuperOptics(PTank obj) : Decorator(obj) {}
    virtual void fire() {
        cout << "super optics " << flush;
        Decorator::fire();
    }
};

class SuperWeapon : public Decorator {
public:
    SuperWeapon(PTank obj) : Decorator(obj) {}
    virtual void fire() {
        cout << "super weapon " << flush;
        Decorator::fire();
    }
};

class SuperGun : public Decorator {
public:
    SuperGun(PTank obj) : Decorator(obj) {}
    virtual void fire() {
        cout << "super gun " << flush;
        Decorator::fire();
    }
};

void zad5() {
    PTank tank =
        PTank(new SuperGun(PTank(new SuperWeapon(PTank(new SuperOptics(PTank(new NormalTank)))))));
    tank->fire();
    cout << endl;

}
int main() {
  cout << "zad1"<<endl;
  zad1();
  cout << endl;
  cout << "zad2"<<endl;
  zad2();
  cout << endl;
  cout << "zad3"<<endl;
  zad3();
  cout << endl;
  cout << "zad4"<<endl;
  zad4();
  cout << endl;
  cout << "zad5"<<endl;
  zad5();
  cout << endl;
  return 0;
}
