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

#include <boost/algorithm/string.hpp>

using namespace std;


// ---------------------------------------------------------------------------------*
//  * ZAD1 - uzycie wizytatora
//  * ZAD2 - most
//  *----------------------------------------------------------------------------------

using cstr = const string&;

class Language {
public:
    virtual bool compare(char c1, char c2) = 0;
    virtual char digitSep() const = 0;
    virtual void printName(ostream& os, cstr& first, cstr& family) = 0;
};
using PLang = shared_ptr<Language>;
class Polish : public Language {
public:
    virtual bool compare(char c1, char c2) { return false; } //brak implementacji
    virtual char digitSep() const { return ','; }
    virtual void printName(ostream& os, cstr& first, cstr& family) { os << first << ' ' << family; }
};
class Chinese : public Language {
public:
    virtual bool compare(char c1, char c2) { return false; } //brak implementacji
    virtual char digitSep() const { return '.'; }
    virtual void printName(ostream& os, cstr& first, cstr& family) {
        string str(family);
        boost::to_upper(str);
        os << str << ' ' << first;
    }
};

class Paragraph;
class List;
class Name;

class Visitor {
public:
    virtual void visit(const Paragraph& p) = 0;
    virtual void visit(const List& l) = 0;
    virtual void visit (const Name& t) = 0;
};

class Element {
public:
    Element(PLang l) : lang_(l) {}
    virtual void accept(Visitor& v) const = 0;
    PLang getLang() const { return lang_; }
protected:
    PLang lang_;
};
class Paragraph : public Element {
public:
    Paragraph(PLang l, cstr s) : Element(l), str_(s) {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
    cstr get() const { return str_; }
private:
    string str_;
};
class List : public Element {
public:
    List(PLang l) : Element(l) {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
private:
    vector<Paragraph> items_;
};

class Name : public Element {
public:
    Name(PLang l, cstr first, cstr family ) : Element(l), first_(first), family_(family) {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
    cstr getFirst() const { return first_; }
    cstr getFamily() const { return family_; }
private:
    string first_;
    string family_;
};

struct Statistics : public Visitor {

    Statistics() : p_(0), l_(0), n_(0) {}
    virtual void visit(const Paragraph& ){ ++p_; }
    virtual void visit(const List& l) { ++l_; }
    virtual void visit (const Name& n) { ++n_; }

    int p_, l_, n_;
};

Statistics read_statistics(const vector<Element*>& v) {
    Statistics stats;
    for(Element* e : v)
        e->accept(stats);
    return stats;
}

void zad1() {
    PLang pl(new Polish); //nieistotne dla zad1
    PLang cn(new Chinese); //nieistnotne dla zad1

    Paragraph p1(pl, "XXX"), p2(cn, "YYYY");
    List l1(pl), l2(cn);
    Name n1(pl, "Adam", "Abacki"), n2(cn, "Jing", "Jang");

    vector<Element*> v = { &p1, &l1, &p2, &n1, &n2, &l2 };
    Statistics s = read_statistics(v);
    cout << s.p_ << ", " << s.l_ << ", " << s.n_ << endl;


    //tutaj przyklad dla zad2
    struct Print : public Visitor {
        virtual void visit(const Paragraph& p){ cout << p.get(); }
        virtual void visit(const List& l) { }
        virtual void visit (const Name& n) { n.getLang()->printName(cout, n.getFirst(), n.getFamily() ); }
    } print;
    for(Element* e : v)
        e->accept(print);
    cout << endl;

}

// -------------------------------------------------------------*
//  *   ZAD3 podaj wydruk
//  *--------------------------------------------------------------
class E : public std::exception { };

class B {
public:
    B(int i) : i_(i) { cout << i_; }
    virtual ~B() { cout << i_; }
    int get() const { return i_; }
private:
    int i_;
};

class D1 : virtual public B {
public:
    D1(int i) : B(i+1) {}
};
class D2 : virtual public B {
public:
    D2(int i) : B(i+2) {}
};

class M :  public D1, public D2 {
public:
    M(int i) : B(i), D1(i), D2(i) {}
    int get() const { return D1::get() + D2::get(); }
};

string NAME = "XXXXX";

extern void g(int i);

void f(int i, const M& m) {
    if(m.get() < 0) throw E();
    g(i+3);
}

void g(int i) {
    const int N = NAME.size() % 3;
    M m(N-i);
    f(i, m);
}

void zad3() {
    try {
        g(1);
    } catch(E&){
    }
    cout << endl;
}

void zad3x() {
    cout << endl;
    for(int i=3;i<15;++i) {
        NAME = std::string(i, 'x');
        cout << "name: " << NAME << ":";
        zad3();
        cout << endl;
    }
}

/*-------------------------------------------------------------------------------------*
 *                                                                                     *
 * ZAD4 - użycie słabych/silnych sprytnych wskaźników                   *
 *-------------------------------------------------------------------------------------*/

struct Node;
using PNode = shared_ptr<Node>;
using QNode = weak_ptr<Node>;

struct Node {
    Node() { cout << "c-tor" << endl;}
    ~Node() { cout << "d-tor" << endl;}

    QNode up_;
    PNode down_;
    PNode left_;
    QNode right_;
};

struct Grid {
    Grid() : topLeft_(new Node) { cout << "grid c-tor" << endl; }
    ~Grid() { cout << "grid d-tor" << endl; }

    PNode topLeft_;
};

void connectDown(PNode up, PNode down) {
    up->down_ = down;
    down->up_ = up;
}
void connectLeft(PNode right, PNode left) {
    right->left_ = left;
    left->right_ = right;

}


void zad4()  {
    Grid g;

    connectLeft(g.topLeft_, PNode(new Node));
    connectDown(g.topLeft_, PNode(new Node));
    connectLeft(g.topLeft_->down_, PNode(new Node));
    connectDown(g.topLeft_->left_, g.topLeft_->down_->left_ );
};

//możliwe wycieki pamięci
//widget* load_widget( widget::id desired );

//wiadomo, kto jest właścicielem
//unique_ptr<widget> load_widget( widget::id desired );
//wiadomo, kto jest właścicielem
//shared_ptr<widget>    load_widget( widget::id desired );


void zad5() {
}

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