#include <string>
#include <vector>
#include <map>

#include <memory>
#include <iostream>
#include <fstream>


using namespace std;

class Drink {
public:
    virtual void print() const = 0;
    virtual void save(ostream& os) const = 0;
    virtual void load(istream& is) = 0;
    virtual ~Drink() = default;
};
using UDrink = unique_ptr<Drink>;
class Tea : public Drink {
public:
    static const int ID = 1;

    Tea() : desc_("Normal tea") {}
    Tea(const string& desc) : desc_(desc) {}

    void print() const override { cout << "Tea" << endl; }
    void save(ostream& os) const override { os << ID << endl << desc_ << endl; }
    void load(istream& is) override { getline(is, desc_); }
private:
    string desc_;
};
UDrink createTea() { return UDrink(new Tea); }
class Coffee : public Drink {
public:
    static const int ID = 2;

    Coffee() : desc_("Normal coffee") {}
    Coffee(const string& desc) : desc_(desc) {}

    void print() const override { cout << "Coffee" << endl; }
    void save(ostream& os) const override { os << ID << endl << desc_ << endl; }
    void load(istream& is) override { getline(is, desc_); }
private:
    string desc_;
};
UDrink createCoffee() { return UDrink(new Coffee); }


//fabryka, singleton

class DrinkFactory {
private:
    DrinkFactory() {}; //singleton
public:
    static DrinkFactory& getInstance() {
        static DrinkFactory factory;
        return factory;
    }

    DrinkFactory(const DrinkFactory&) = delete;

    using CreateFun = UDrink (*)();
    using Map = std::map<int, CreateFun>;

    void add(int id, CreateFun fun) { //register new pair (id, createfun
        creators_.insert({id, fun});
    }

    UDrink create(int id) {
        Map::const_iterator i = creators_.find(id);
        if(i != creators_.end() )
            return (i->second)();
        else
            return UDrink(new Tea); //if not found return tea
    }
private:
    Map creators_;
};


using Drinks = vector<UDrink>;

class DrinksFile {
public:
    DrinksFile(const string& filename) : filename_(filename) {}

    void save(const Drinks& drinks) {
        ofstream f(filename_);
        for(const UDrink& d : drinks) {
            d->save(f);
        }
    }

    Drinks load() {
        Drinks vec;
        try {
            ifstream f;
            f.open(filename_);

            for( string line; getline( f, line ); ) { //read file line-by-bline
                int id = stoi(line); // string -> integer
                UDrink drink = DrinkFactory::getInstance().create(id);
                drink->load( f );
                vec.push_back( std::move(drink) );
            }

        } catch(std::exception& e) {
            cerr << e.what() << endl;
        }
        return vec;
    }
private:
    string filename_;
};


int main() {
    DrinkFactory::getInstance().add(Coffee::ID, createCoffee);
    DrinkFactory::getInstance().add(Tea::ID, createTea);


    Drinks d;
    d.push_back(createTea());
    d.push_back(createTea());
    d.push_back(createCoffee());

    DrinksFile df("drinks.txt");
    df.save(d);

    Drinks dd = df.load();
    for(const UDrink& u : dd ) {
        u->print();
    }
    return 0;
}
