#include <string>
#include <iostream>
#include <memory>
#include <exception>


/**-------------------------------------------------------------*
wizytator - rozne komunikaty w roznych jezykach, zmiana kolejnosci slow

"uzytkownik ID nie ma uprawnien"
"no permission for user ID"

"błąd polaczenia sieciowego"
"network error"

"błąd wewnętrzny serwera"
"inrernal server error"

   *--------------------------------------------------------------*/

class PermissionException;
class NetworkException;
class ServerException;


class Visitor {
public:
    virtual void visit(const PermissionException& e) = 0;
    virtual void visit(const NetworkException& e) = 0;
    virtual void visit(const ServerException& e) = 0;
};

//klasa bazowa dla bledow
class Exception : public std::exception {
public:
    virtual void accept(Visitor& v) const = 0;
    virtual const char* what() { return "base exception"; }
    virtual ~Exception() {}
};

//wyjatek - brak uprawnien
class PermissionException : public Exception {
public:
    PermissionException(int user_id = 0) : userId(user_id) {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
    int getUserId() const { return userId; }
private:
    int userId; //identyfikator uzytkownika, ktory nie mial uprawnien
};

//wyjatek - blad wewnetrzny serwera
class NetworkException : public Exception {
public:
    NetworkException() {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
};


//wyjatek - blad wewnetrzny serwera
class ServerException : public Exception {
public:
    ServerException() {}
    virtual void accept(Visitor& v) const { v.visit(*this); }
};


using Translator = Visitor;

//class Translator {};

class PolishTranslator : public Translator {
public:
    virtual void visit(const PermissionException& e) {
        std::cerr << u8"Użytkownik " << e.getUserId() << u8" nie ma uprawnień" << std::endl;
    }
    virtual void visit(const NetworkException& e) {
        std::cerr << u8"Błąd połączenia sieciowego" << std::endl;
    }
    virtual void visit(const ServerException& e) {
        std::cerr << u8"Błąd wewnętrzny serwera" << std::endl;
    }
};


class EnglishTranslator : public Translator {
public:
    virtual void visit(const PermissionException& e) {
        std::cerr << "No permission for user " << e.getUserId() << std::endl;
    }
    virtual void visit(const NetworkException& e) {
        std::cerr << "Network error" << std::endl;
    }
    virtual void visit(const ServerException& e) {
        std::cerr << "Internal server error" << std::endl;
    }
};

class Settings {
public:
    static Settings& getInstance() {
        static Settings instance;
        return instance;
    }
    void setLanguage(const std::string& l) { lang = l; }
    Translator& getTranslator() {
        //kod tej metody jest niepoprawny - powinna byc tutaj fabryka
        //nie wpylwa to na zadanie i proszę tego nie poprawiać
        if(lang == "PL") return *polishTranslator_;
        else return *englishTranslator_;
    }

private:
    static Settings* instance;
    Settings(const Settings&) = delete;
    Settings& operator=(const Settings&) = delete;

    Settings() {}
    ~Settings() { delete englishTranslator_; delete polishTranslator_; }

    std::string lang = "EN";
    //uwaga! przechowywanie obiektow do translacji, w pokazany nizej sposob, brak zwalaniania to blad, ale prosze tego nie poprawiac.
    Translator* englishTranslator_ = new EnglishTranslator;
    Translator* polishTranslator_ = new PolishTranslator;
};


void printError(const Exception& e) {
    Translator& t = Settings::getInstance().getTranslator();
    e.accept(t);
}

int main() {
    try {
        throw PermissionException(13);
    } catch(Exception& e) {
        printError(e);
    }
    try {
        throw NetworkException();
    } catch(Exception& e) {
        printError(e);
    }
    try {
        throw ServerException();
    } catch(Exception& e) {
        printError(e);
    }

    Settings::getInstance().setLanguage("PL");

    try {
        throw PermissionException(13);
    } catch(Exception& e) {
        printError(e);
    }
    try {
        throw NetworkException();
    } catch(Exception& e) {
        printError(e);
    }
    try {
        throw ServerException();
    } catch(Exception& e) {
        printError(e);
    }

    return 0;
}
