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

using namespace std;

/**-------------------------------------------------------------*
   ZAD1 lista ze sprytnymi wskaźnikami
   *--------------------------------------------------------------*/

class List {
    struct Node;
    typedef std::shared_ptr<Node> PNode;
    typedef std::weak_ptr<Node> PWNode;
public:
    List() : head_(nullptr) {}
    ~List();
    void push_front(int val);
private:
    Node* head_;
};

struct List::Node {
    Node(int v = 0) : val_(v), next_(nullptr), prev_(nullptr)
    {
        cout << "c-tor:" << val_ << endl;
    }
    ~Node() {
        cout << "d-tor:" << val_ << endl;
    }
    int val_;
    Node* next_;
    Node* prev_;
};

List::~List() {
    Node* n = head_;
    while( n != nullptr ) {
        Node* p = n;
        n = n->next_;
        delete p;
    }
}

void List::push_front(int val) {
    Node* n = new Node(val);
    if(head_)
        head_->prev_ = n;
    n->next_ = head_;
    head_ = n;
}

class List2 {
    struct Node;
    typedef std::shared_ptr<Node> PNode;
    typedef std::weak_ptr<Node> PWNode;
public:
    List2() {}
    ~List2() {}
    void push_front(int val);
private:
    PNode head_;
};

struct List2::Node {
    Node(int v = 0) : val_(v)
    {
        cout << "c-tor:" << val_ << endl;
    }
    ~Node() {
        cout << "d-tor:" << val_ << endl;
    }
    int val_;
    PNode next_;
    PWNode prev_;
};

void List2::push_front(int val) {
    PNode n(new Node(val));
    if(head_)
        head_->prev_ = n;
    n->next_ = head_;
    head_ = n;
}


void zad1() {
    List2 l;
    l.push_front(3);
    l.push_front(2);
    l.push_front(1);
}

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

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

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

typedef unique_ptr<F> PF;

void f(const PF& pf) {
    if(pf->i_ > 2) {
        f( PF(new F(pf->i_ / 2)));
    }
    else {
        throw E(pf->i_);
    }
}


int LICZBA_LITER_NAZWISKA = 5;

void zad2() {
    for(int i=3;i<10;++i) {
        LICZBA_LITER_NAZWISKA = i;
        try {
            PF pb(new F(LICZBA_LITER_NAZWISKA));
            f(pb);
        } catch(E& e){
            cout << e.i_;
        }
        cout << endl;
    }
}

/**-------------------------------------------------------------*
 * TODO ZAD3 - dziedziczenie wielobazowe                             *
 *--------------------------------------------------------------*/

class B {
    int i_;
public:
    B(int i) : i_(i) {}
    int inc() { return doInc(); }
protected:
    virtual int doInc() { return ++i_; }

};

class D1 : public B {
public:
    D1(int i) : B(i-1) {}
protected:
    virtual int doInc() { return B::doInc(); }
};

class D2 : public B {
public:
    D2(int i) : B(i-2) {}
protected:
    virtual int doInc() { return 3; }
};

class M :  public D1, public D2 {
public:
    M(int i) : D1(i), D2(i) {}
protected:
    virtual int doInc() { return D1::doInc() + D2::doInc(); }
};


void zad3() {
    for(int i=3;i<10;++i) {
        LICZBA_LITER_NAZWISKA = i;
        M m(LICZBA_LITER_NAZWISKA);
        cout << static_cast<D1&>(m).inc();
        cout << static_cast<D2&>(m).inc();
        cout << endl;
    }
}

/**-------------------------------------------------------------*
TODO   ZAD4 fabryka abstrakcyjna dla komendy i komendy odwrotnej
   *--------------------------------------------------------------*/
class Command {
public:
    virtual void execute() = 0;
};

typedef shared_ptr<Command> PCommand;

class History {
public:
    list<PCommand> getHistory() { return commands_; }
    list<PCommand> getHistoryReverse() { return reverse_; }
    void add(PCommand cmd, PCommand rev) {
        commands_.push_back(cmd);
        reverse_.push_front(rev);
    }
private:
    list<PCommand> commands_;
    list<PCommand> reverse_;
};

typedef shared_ptr<History> PHistory;

class LightOnCommand : public Command {
public:
    virtual void execute() { cout << "light on" << endl; }
};

class LightOffCommand : public Command {
public:
    virtual void execute() { cout << "light off" << endl; }
};

class FanUpCommand : public Command {
public:
    virtual void execute() { cout << "fan up" << endl; }
};

class FanDownCommand : public Command {
public:
    virtual void execute() { cout << "fan down" << endl; }
};

class AbstractFactory {
public:
    virtual PCommand createCmd() = 0;
    virtual PCommand createReverse() = 0;
};

class LightFactory : public AbstractFactory {
public:
    virtual PCommand createCmd() { return PCommand(new LightOnCommand); }
    virtual PCommand createReverse() { return PCommand(new LightOffCommand); }
};

class FanFactory : public AbstractFactory {
public:
    virtual PCommand createCmd() { return PCommand(new FanUpCommand); }
    virtual PCommand createReverse() { return PCommand(new FanDownCommand); }
};

typedef shared_ptr<AbstractFactory> PFactory;

class Button {
public:
    Button(PFactory factory, PHistory history) : factory_(factory), history_(history) {}

    void click() {
        PCommand cmd = factory_->createCmd();
        cmd->execute();
        history_->add(cmd, factory_->createReverse() );
    }
private:
    PFactory factory_;
    PHistory history_;
};


void zad4() {
    PHistory history(new History);

    Button button1(PFactory(new LightFactory), history);
    Button button2(PFactory(new FanFactory), history);

    button1.click();
    button1.click();
    button2.click();

    for(PCommand rev_cmd : history->getHistoryReverse() ) {
        rev_cmd->execute();
    }

}

/**-------------------------------------------------------------*
ZAD 5 wizytator - rozne komunikaty w roznych jezykach, zmiana kolejnosci slow
"blad poziomu N"
"N level error"
   *--------------------------------------------------------------*/

class UnknownErrorException;
class LevelErrorException;

class Visitor {
public:
    virtual void visit(const LevelErrorException& e) = 0;
    virtual void visit(const UnknownErrorException& e) = 0;
};

class BaseException : public std::exception {
public:
    virtual void accept(Visitor& v) const = 0;
    virtual const char* what() { return "base exception"; }
    virtual ~BaseException() throw() {}
};

class LevelErrorException : public BaseException {
public:
    LevelErrorException(int level)  : level_(level) {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
    int getLevel() const { return level_; }
private:
    int level_;
};

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


class PolishPrintVisitor : public Visitor {
public:
    PolishPrintVisitor(ostream& os) : os_(os) {}
    virtual void visit(const LevelErrorException& e) {
        os_ << "Blad poziomu " << e.getLevel() << endl;
    }
    virtual void visit(const UnknownErrorException& e) {
        os_ << "Blad bez opisu" << endl;
    }
    ostream& os_;
};

class EnglishPrintVisitor : public Visitor {
public:
    EnglishPrintVisitor(ostream& os) : os_(os) {}
    virtual void visit(const LevelErrorException& e) {
        os_ << e.getLevel() << " level error" << endl;
    }
    virtual void visit(const UnknownErrorException& e) {
        os_ << "unknown error" << endl;
    }
    ostream& os_;
};

void printEnglishError(const BaseException& e) {
    EnglishPrintVisitor v(cout);
    e.accept(v);
}

void printPolishError(const BaseException& e) {
    PolishPrintVisitor v2(cout);
    e.accept(v2);
}

class Logger {
public:
    static Logger& getInstance() {
        static Logger logger;
        return logger;
    }

    void printError(const BaseException& e) {
        if(language_ == "pl")
            printPolishError(e);
        else
            printEnglishError(e);
    }
    void setLanguage(string l) { language_ = l; }
private:
    Logger() : language_("en") {}
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    string language_;
};

void zad5() {
    try {
        throw LevelErrorException(3);
    } catch(BaseException& e) {
        Logger::getInstance().printError(e);
    }
    try {
        throw UnknownErrorException();
    } catch(BaseException& e) {
        Logger::getInstance().printError(e);
    }
    Logger::getInstance().setLanguage("pl");
    try {
        throw LevelErrorException(3);
    } catch(BaseException& e) {
        Logger::getInstance().printError(e);
    }
    try {
        throw UnknownErrorException();
    } catch(BaseException& e) {
        Logger::getInstance().printError(e);
    }
}

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;
}
