//Kolokwium ZPR, 2021.04.13 - sa tutaj 2 polecenia ogolne (linia 3 i linia 8)

//1. Prosze umiescic swoje imie i nazwisko

const char* IMIE = "ROBERT";
const char* NAZWISKO = "NOWAK";

//2. Prosze oznaczyc linie, ktore zostaly zmodyfikowane dodajac krotki komentarz

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

using namespace std;

// ZAD1, 7 pkt
//
//Obiekt klasy ErrorManager (singleton) odpowiada za przesłanie komunikatów o bledach, użytkownik woła metodę msg przekazując treść komunikatu.
//Treść ta jest zapisywana do odpowiednich miejsc, w zależności od konfiguracji. Przedstawione poniżej rozwiązanie
//ma pewne ograniczenia: (1) klasa jest zależna jest od wszystkich klas reprezentujących odbiorców komunikatu (Mail, File, cerr),
//(2) konfiguracja odbiorców komunikatów jest ograniczona, na przykład nie można zapisywać do więcej niż dwóch plików,
// nie można wyłączyć wysyłania komunikatów na strumień cerr itd.

//Popraw kod klasy Logger, aby zlikwidować wady (1) i (2). Możesz dodać nowe klasy, jeżeli jest to potrzebne.

//Klasa pomocnicza, jej nie zmieniamy
class File {
public:
    File(const std::string& file_name) {}
    void write(const std::string& msg) {}
};

//Klasa pomocnicza, jej nie zmieniamy
class Mail {
public:
    Mail(const std::string& address) {}
    void send(const std::string& msg) {}
};

//ta klasa jest do poprawy
class ErrorManager {
public:
    static ErrorManager& getInstance() {
        static ErrorManager manager;
        return manager;
    }

    void setFile(File* f) { file = f; }
    void setMail(Mail* e) { mail = e; }

    void msg(const string& msg) {
        ostringstream os;
        os << ++counter << ':' << msg << std::endl;
        std::cerr << os.str();
        if(file)
            file->write(os.str());
        if(mail)
            mail->send(os.str());
    }
private:
    ErrorManager() : counter(0), file(nullptr), mail(nullptr) {}
    ErrorManager(const ErrorManager&) = delete;
    ErrorManager& operator=(const ErrorManager&) = delete;

    int counter;
    File* file;
    Mail* mail;
};

class Observer {
public:
    virtual void notify(const string& msg) = 0;
};

class FileObs : public Observer {
public:
    FileObs(const string& file_name) : f_(file_name) {}
    virtual void notify(const string& msg) { f_.write(msg); }
private:
    File f_;
};

class MailObs : public Observer {
public:
    MailObs(const string& address) : m_(address) {}
    virtual void notify(const string& msg) { m_.send(msg); }
private:
    Mail m_;
};

class StreamObs : public Observer {
public:
    StreamObs(std::ostream& o) : os(o) {}
    virtual void notify(const string& msg) { os << msg << std::flush; }
private:
    std::ostream& os;
};

class ErrorManager2 {
public:
    static ErrorManager2& getInstance() {
        static ErrorManager2 manager;
        return manager;
    }

    using Observers = vector<Observer*>;

    void reg(Observer* o) { observers.push_back(o); }

    void msg(const string& msg) {
        ostringstream os;
        os << ++counter << ':' << msg << std::endl;

        for(Observer* o : observers)
            o->notify(os.str());
    }
private:
    ErrorManager2() : counter(0) {}
    int counter;
    Observers observers;
};

void  zad1() {
    FileObs obs_file1("file1");
    FileObs obs_file2("file2");
    MailObs obs_mail("address");
    StreamObs obs_cerr(std::cerr);

    ErrorManager2::getInstance().reg(&obs_file1);
    ErrorManager2::getInstance().reg(&obs_file2);
    ErrorManager2::getInstance().reg(&obs_mail);
    ErrorManager2::getInstance().reg(&obs_cerr);

    ErrorManager2::getInstance().msg("test");
}

//ZAD2, 4 pkt

// Ponizej przedstawiona lista jednokierunkowa przechowuje liczby calokowite.
// Obserwujemy nieprawidlowe zwalnianie obiektow, przyklad - kod funkcji zad2().
// Popraw kod.

class List {

    struct Element;
    using PElement = shared_ptr<Element>;
    using PWElement = weak_ptr<Element>;

    struct Element {
        Element(int v) : val_(v), next_() { cout << "c-tor " << val_ << endl; }
        ~Element() { cout << "d-tor " << val_ << endl; }
        int val_;
        PElement next_; //zmiana na silny sprytny wskaznik
    };
public:
    List() {}
    ~List() { }
    void push_front(int v) {
        PElement e = make_shared<Element>(v);
        e->next_ = head_;
        head_ = e;
    }
private:
    PElement head_;
};

void zad2() {
    List l;
    l.push_front(1);
    l.push_front(2);
    l.push_front(3);
}

//ZAD3, 6 pkt
//
// Dla hierarchii zwierząt (klasa bazowa Animal, klasy pochodne Dog, Cat, Snake)
// dodaj dwie funkcje 'num_mammals' i 'voices'.
// Staraj się nie modyfikować klas w hierarchii klas.
// Dla przedstawionego przykładu funkcja 'num_mammals' powinna zwrócić 3, zaś funkcja 'voices' napis "Hau Miau Sss Hau"

class Visitor;

class Animal {
public:
    Animal(const string& name) : name_(name) {}
    virtual void accept(Visitor& v)  = 0;
    const string& getName() const { return name_; }
private:
    string name_;
};

class Dog;
class Cat;
class Snake;

class Visitor {
public:
    virtual ~Visitor() {}
    virtual void visit(Dog&) = 0;
    virtual void visit(Cat&) = 0;
    virtual void visit(Snake&) = 0;
};

class Dog : public Animal {
public:
    Dog(const string& name) : Animal(name) {}
    virtual void accept(Visitor& v) { v.visit(*this); }
};

class Cat : public Animal {
public:
    Cat(const string& name) : Animal(name) {}
    virtual void accept(Visitor& v) { v.visit(*this); }
};

class Snake : public Animal {
public:
    Snake(const string& name) : Animal(name) {}
    virtual void accept(Visitor& v) { v.visit(*this); }
};

using PAnimal = shared_ptr<Animal>;
using Animals = vector<PAnimal>;

//imiona zwierzat rozdzielone spacją
string names(const Animals& animals) {
    string names;
    for(PAnimal a : animals)
        names += a->getName() + ' ';
    return names;
}

//odglosy zwierzat rozdzielone spacją, pies ma 'Hau', kot 'Miau', wąż 'Sss'
string voices(const Animals& animals) {
    struct V : public Visitor {
        virtual void visit(Dog& ){ voices += "Hau "; }
        virtual void visit(Cat& ){ voices += "Miau "; }
        virtual void visit(Snake& ) { voices += "Sss "; }
        string voices;
    } vis;
    for(PAnimal a : animals)
        a->accept(vis);
    return vis.voices;
}

//liczba ssaków, pies i kot to ssak, wąż to nie ssak
int num_mammals(const Animals& animals) {
    struct V : public Visitor {
        V() : counter(0) {}
        virtual void visit(Dog& ){ ++counter; }
        virtual void visit(Cat& ){ ++counter; }
        virtual void visit(Snake& ) { }
        int counter;
    } vis;
    for(PAnimal a : animals)
        a->accept(vis);
    return vis.counter;
}

void zad3() {
    Animals animals;
    animals.push_back(PAnimal(new Dog("Burek")));
    animals.push_back(PAnimal(new Cat("Mruczek")));
    animals.push_back(PAnimal(new Snake("Python")));
    animals.push_back(PAnimal(new Dog("Reksio")));

    cout << names(animals) << endl;
    cout << num_mammals(animals) << endl;
    cout << voices(animals) << endl;
}

// ZAD4 4 pkt
// Popraw implementacje klasy Matrix.
// Dodano 'get' dla stałej
// ale najbardziej istotne, to należy rozdzielić wyświetlanie od operacji dla macierzy

class Matrix {
public:
    Matrix(unsigned int n = 2) : N(n) {
        if(n == 0) throw std::out_of_range("Matrix::c-tor");
        tab = new double[N*N];
        for(unsigned int i = 0; i < N*N; ++i) tab[i] = 0.0;
    }
    Matrix(const Matrix& m) : N(m.N), tab(new double[N*N]) {
        for(unsigned int i = 0; i < N*N; ++i) tab[i] = m.tab[i];
    }
    ~Matrix() { delete [] tab; }

    unsigned int size() const { return N; }

    double& get(unsigned int i, unsigned int j) {
		return const_cast<double&>(static_cast<const Matrix*>(this)->get(i,j) );
    }

	const double& get(unsigned int i, unsigned int j) const {
        if(i >= N || j >= N) throw std::out_of_range("Matrix::get");
        return tab[i*N + j];
	}

    // void print() const {
    //     for(unsigned int i = 0; i < N; ++i ) {
    //         cout << '[' << tab[i*N];
    //         for(unsigned int j = 1; j < N; ++j ) cout << "," << tab[i*N+j];
    //         cout << ']' << endl;
    //     }
    // }

    // void prettyPrint() const {
	// 	cout << "Matrix size:" << N << endl;
	// 	print();
    // }

private:
    const unsigned int N;
    double* tab;
};

void print(const Matrix& m) {
	for(unsigned int i = 0; i < m.size(); ++i) {
		cout << '[' << m.get(i,0);
		for(unsigned int j = 1; j < m.size(); ++j ) cout << "," << m.get(i,j);
		cout << ']' << endl;
	}
}

void zad4() {
    Matrix m(3);
    m.get(0,0) = 1;
    m.get(1,1) = 1;
    m.get(2,2) = 1;
    print(m);
}

// ZAD5 4 pkt
// W miejsce X, Y, Z wstaw liczby naturalne aby 'zad5', drukowała
// (1) liczbe liter Twojego imienia. Imię wpisales do stalej IMIE w 5 linii tego pliku.
// (2) liczbe liter Twojego nazwiska. Nazwisko wpisales do stalej NAZWISKO w 6 linii tego pliku.

class A {
public:
    int fx() { return dofx(); }
private:
    virtual int dofx(){ return 1; }
};

class B {
public:
    int gx() { return dogx(); }
private:
    virtual int dogx(){ return 1; }
};

class C : public A, public B { };

class D : public C {
    virtual int dofx(){ return 5; }
    virtual int dogx(){ return 4; }
};

class E : public C {
    virtual int dofx(){ return 0; }
    virtual int dogx(){ return 0; }
};

class F : public C {
    virtual int dofx(){ return 0; }
    virtual int dogx(){ return 0; }
};


void zad5() {
    C c;
    D d;
    E e;
    F f;
    cout << c.fx() + d.fx() + e.fx() + f.fx() <<  endl;
    cout << c.gx() + d.gx() + e.gx() + f.gx() << endl;
}

int main() {
    cout << "Kolokwium ZPR, rozwiazania " << IMIE << " " << NAZWISKO << endl;
    cout << "zad1"<<endl;
    zad1();
    cout << "zad2"<<endl;
    zad2();
    cout << "zad3"<<endl;
    zad3();
    cout << "zad4"<<endl;
    zad4();
    cout << "zad5"<<endl;
    zad5();
    cout << endl;
    return 0;
}
