#include <vector>
#include <memory>
#include <iostream>
#include <functional>


using namespace std;


using Money = int;
using Id = int;

class Staff;
using UStaff = unique_ptr<Staff>;      //
using VStaff = Staff*;                 //widok, nie zarzadza obiektem, z mozliwoscia przechowywania w std::vector
//UWAGA, w C++17 powinno być
//using VStaff = reference_wrapper<Staff>;

class Employee;
class Trainee;
class Manager;

class Visitor {
public:
    virtual void visit(const Employee& e) = 0;
    virtual void visit(const Trainee& t) = 0;
    virtual void visit(const Manager& m) = 0;
};

class HRSystem;

//hierarchia osob zatrudnionych
class Staff {
public:
    Staff(HRSystem& hr, Id id) : hr_(hr), id_(id) { }
    virtual ~Staff() {}

    virtual void accept(Visitor& v) const = 0;

    virtual void addSubordinate(const VStaff& s) {}
    HRSystem& hr_;
    Id id_;
};


class Employee : public Staff {
public:
    Employee(HRSystem& hr, Id id) : Staff(hr, id) {}
    void accept(Visitor& v) const override { v.visit(*this); }
};

class Trainee : public Staff {
public:
    Trainee(HRSystem& hr, Id id) : Staff(hr, id){}
    void accept(Visitor& v) const override { v.visit(*this); }
};

class Manager : public Staff {
public:
    Manager(HRSystem& hr, Id id) : Staff(hr, id) {}

    void accept(Visitor& v) const override { v.visit(*this); }

    void addSubordinate(const VStaff& s) override {
        sub_.push_back(s);
    }
    vector<VStaff> sub_;
};

class HRSystem {
public:
    HRSystem() : staffId_(0), baseSalary_(4242) {}

    VStaff createEmployee(); //nowy pracownik etatowy
    VStaff createTrainee();  //nowy stazysta
    VStaff createManager();  //nowy kierownik

    Money getBaseSalary() const { return baseSalary_; }
    void setBaseSalary(Money m) { baseSalary_ = m; }

    vector<UStaff> staff_;
private:
    int staffId_;
    Money baseSalary_;


};
VStaff HRSystem::createEmployee() {
    Id id = ++staffId_;
    staff_.push_back( make_unique<Employee>(*this, id) );
    return staff_.back().get();
}

VStaff HRSystem::createTrainee() {
    Id id = ++staffId_;
    staff_.push_back( make_unique<Trainee>(*this, id) );
    return staff_.back().get();
}

VStaff HRSystem::createManager() {
    Id id = ++staffId_;
    staff_.push_back( make_unique<Manager>(*this, id) );
    return staff_.back().get();
}

static const double TraineeFactor = 0.8;
static const double EmployeeFactor = 1.1;
static const double ManagerFactor = 1.0;
static const double ManagerSubFactor = 0.3;

class VisitorSalary : public Visitor {
public:
    VisitorSalary() : salary_(0) {}

    Money getSalary() { return salary_; }

    void visit(const Employee& e) override {
        salary_ = static_cast<Money>( e.hr_.getBaseSalary() * EmployeeFactor );
    }
    void visit(const Trainee& t) override {
        salary_ = static_cast<Money>( t.hr_.getBaseSalary() * TraineeFactor );
    }
    void visit(const Manager& m) override {
        Money sum = 0;
        for(VStaff sub : m.sub_) {
            VisitorSalary v;
            sub->accept(v);
            sum += v.getSalary();
        }
        salary_ = static_cast<Money>( m.hr_.getBaseSalary() * ManagerFactor )
            + static_cast<Money>( sum * ManagerSubFactor );
    }
private:
    Money salary_;
};


int main() {
    HRSystem hr;
    VStaff m =  hr.createManager();
    VStaff e1 = hr.createEmployee();
    VStaff e2 = hr.createEmployee();
    m->addSubordinate(e1);
    m->addSubordinate(e2);
    VStaff t = hr.createTrainee();

    hr.setBaseSalary(5000);

    for(const UStaff& s : hr.staff_ ) {
        VisitorSalary v;
        s->accept(v);
        cout << s->id_ << ":" << v.getSalary() << endl;
    }
    return 0;
}
