#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
#include<map>
#include<list>
#include<iostream>
#include<stdint.h>
using namespace std;
using namespace boost;

class Employee {
    public:
        Employee(uint64_t id, string name);
        void print() const; // Should be virtual
        uint64_t getId();
        const string &getName(); // should be const
        virtual const string getTitle(); // should be const
	uint64_t getSalary();
	void setSalary(uint64_t s);
    private:
        uint64_t id_;
        const string name_;
	uint64_t salary_;
}
/*
 * The above missing ; yields the following error:
 * bad_db.cpp:10: error: new types may not be defined in a return type
 * bad_db.cpp:10: note: (perhaps a semicolon is missing after the definition of "Employee")
 * bad_db.cpp:30: error: return type specification for constructor invalid
 */
Employee::Employee(uint64_t id, string name)
    : id_(id), name_(name), salary_(0)
{ }
void Employee::print() const {
    cout << getTitle() << ":" << endl;
    cout << "    " << name_ << endl;
}
uint64_t Employee::getId() { return id_; }
const string &Employee::getName() { return name_; }
const string Employee::getTitle() { return "Basic Employee"; }
uint64_t Employee::getSalary() { return salary_; }
/* This setSalary definition generates the following error, because we forgot to prefix it with
 * Employee::
 * bad_db_tmp.cpp: In function "void setSalary(uint64_t)":
 * bad_db_tmp.cpp:46: error: "salary_" was not declared in this scope
 */
void setSalary(uint64_t s) { salary_ = s; }

class Manager : public Employee {
    public:
        Manager(uint64_t id, string name);
        virtual void print() const;
        void add_report(shared_ptr<Employee> emp);
        virtual const string getTitle();
    protected:
        list<shared_ptr<Employee> > direct_reports_;
};
Manager::Manager(uint64_t id, string name)
    : Employee(id, name)
{}
void Manager::print() const {
    cout << getTitle() << ":" << endl;
    cout << "\t" << getName() << endl;
    cout << "\t" << "Supervises:" << endl;
    if (direct_reports_.empty()) {
        cout << "\t\tNobody" << endl;
    } else {
        /* This use of a non-const iterator in a const iterator in a const method generates the
         * following mysterious error, because the compiler doesn't know begin() won't modify the list:
         * bad_db_tmp.cpp: In member function "virtual void Manager::print() const":
         * bad_db_tmp.cpp:74: error: no match for "operator=" in "it = ((const Manager*)this)->Manager::direct_reports_.std::list<_Tp, _Alloc>::begin [with _Tp = boost::shared_ptr<Employee>, _Alloc = std::allocator<boost::shared_ptr<Employee> >]()"
         * /usr/lib/gcc/i686-redhat-linux/4.4.5/../../../../include/c++/4.4.5/bits/stl_list.h:114: note: candidates are: std::_List_iterator<boost::shared_ptr<Employee> >& std::_List_iterator<boost::shared_ptr<Employee> >::operator=(const std::_List_iterator<boost::shared_ptr<Employee> >&)
         */
        list<shared_ptr<Employee> >::iterator it;
        for(it = direct_reports_.begin(); it != direct_reports_.end(); it++) {
            cout << "\t\t\t" << (*it)->getTitle() << " " << (*it)->getName() << endl;
        }
    }
}
const string Manager::getTitle() { return "Manager"; }
void Manager::add_report(shared_ptr<Employee> e) {
    direct_reports_.push_back(e);
}

class President : public Manager {
    public:
        President(uint64_t id, string name);
        virtual void print() const;// We declare but do not define this virtual method, so we get a linker error about "undefined reference to `vtable for President'"
        virtual const string getTitle();
};
President::President(uint64_t id, string name) : Manager(id, name) {}
const string President::getTitle() { return "President"; }

class Developer : public Employee {
    public:
        Developer(uint64_t id, string name);
        virtual void print() const;
        virtual const string getTitle();
};
Developer::Developer(uint64_t id, string name)
    : Employee(id, name)
{}
void Developer::print() const {
    cout << getTitle() << ":" << endl;
    cout << "\t" << getName() << endl;
}
const string Developer::getTitle() { return "Developer"; }

struct lt_employee
{
    bool operator()(const shared_ptr<Employee> &e1, const shared_ptr<Employee> &e2)
    {
        return e1->getId() < e2->getId();
    }
};

struct lt_uint64
{
    bool operator()(const uint64_t &a, const uint64_t &b)
    {
        return a < b;
    }
};

int main(int argc, char** argv) {
    map<uint64_t,shared_ptr<const Employee>,lt_employee> db;
    map<uint64_t,shared_ptr<const Employee>,lt_uint64>::iterator it;

    Manager *hank = new President(0,"Hank");
    /* Because the map contains shared pointers to const Employees, but we assign an Employee*, we
     * see an error message like this:
     * bad_db_tmp.cpp: In function "int main(int, char**)":
     * bad_db_tmp.cpp:129: error: no match for "operator=" in "db.std::map<_Key, _Tp, _Compare, _Alloc>::operator[] [with _Key = long long unsigned int, _Tp = boost::shared_ptr<const Employee>, _Compare = lt_employee, _Alloc = std::allocator<std::pair<const long long unsigned int, boost::shared_ptr<const Employee> > >](((const long long unsigned int&)((const long long unsigned int*)(&0ull)))) = hank"
     * /usr/include/boost/smart_ptr/shared_ptr.hpp:303: note: candidates are: boost::shared_ptr< <template-parameter-1-1> >& boost::shared_ptr< <template-parameter-1-1> >::operator=(const boost::shared_ptr< <template-parameter-1-1> >&) [with T = const Employee]
     *
     * The error basically tells us that there is no operator= definition for the result of the
     * db[0] lookup (which is a newly-allocated shared pointer to null since we're inserting a new
     * value, hence the &0ull) that takes the same type as the variable 'hank'.  All it can find is
     * an operator= that takes a shared_ptr<const Employee> (this implies hank has the wrong type).
     * We see similar errors on other lines.
     */
    db[0] = hank;
    /* We also see another error on the line above, because we're (implicitly, by inserting for a
     * new key) instantiating a new shared_ptr<const Employee> using a comparator that operates on
     * the map's values, instead of the map's keys; db should also use lt_uint64 as a comparator.
     */
    Manager *steve = new Manager(1,"Steve");  // we should make this a shared_ptr<Manager>; making it shared_ptr<const Manager> would break when we add steve as hank's report, since add_report takes a non-const Employee
    db[1] = steve;
    hank->add_report(steve);
    Employee *colin = new Developer(2,"Colin");
    steve->add_report(colin);
    db[2] = colin;
    Employee *aryan = new Developer(3,"Aryan");
    steve->add_report(aryan);
    db[3] = aryan;

    for(it = db.begin(); it != db.end(); it++) {
        (*it).second->print();
	(*it).second->setSalary(100000); // since we're iterating over const Employees, we can't call setSalary().
    }




    return 0;
}