#include <iostream>
#include <sstream>
#include <memory>
#include <map>
#include <vector>
struct Item
{
public:
std::string id;
std::string name;
double price;
Item(const std::string& id, const std::string& name, double price) : id(id), name(name), price(price) {}
//just to be able to put it in a map
bool operator<(const Item& b) const
{
return price < b.price;
}
friend std::ostream& operator<<(std::ostream& out, const Item& item);
};
std::ostream& operator<<(std::ostream& out, const Item& item)
{
return out << '[' << item.id << "] " << item.name << " (" << item.price << " $)";
}
//indexing the tours by their respective IDs
const std::map<std::string, Item> tours {
{"OH", Item("OH", "Opera House Tour", 300.0)},
{"BC", Item("BC", "Syndney Bridge Climb", 110.0)},
{"SK", Item("SK", "Syndney Sky Tower", 30.0)},
};
class Rule
{
public:
virtual std::map<Item, int> applyDiscount(std::map<Item, int> items) = 0;
};
class FreeSkyTourRule : public Rule
{
std::map<Item, int> applyDiscount(std::map<Item, int> items)
{
auto oh = items.find(tours.at("OH"));
if(oh == items.end())
return items;
auto sk = items.find(tours.at("SK"));
if(sk == items.end())
return items;
Item discount("DC", "Free Sky Discount", -sk->first.price);
items[discount] = sk->second > oh->second ? oh->second : sk->second;
return items;
}
};
class ThreeForTwoRule : public Rule
{
std::map<Item, int> applyDiscount(std::map<Item, int> items)
{
auto oh = items.find(tours.at("OH"));
if(oh == items.end())
return items;
int quantity = oh->second;
Item discount("DC", "Opera House Discount", -oh->first.price);
if(quantity > 2)
items[discount] = oh->second / 3;
return items;
}
};
class BridgeClimbDiscountRule : public Rule
{
std::map<Item, int> applyDiscount(std::map<Item, int> items)
{
auto bc = items.find(tours.at("BC"));
if(bc == items.end())
return items;
int quantity = bc->second;
if(quantity > 4)
{
Item discount("DC", "Bridge Climb Discount", -20.0);
items[discount] = quantity;
}
return items;
}
};
class ShoppingCart
{
private:
std::map<Item, int> items;
std::vector<std::unique_ptr<Rule>> rules;
public:
void addItem(Item item)
{
items[item]++;
}
void addRule(std::unique_ptr<Rule> rule)
{
rules.push_back(std::move(rule));
}
std::map<Item, int> getBill() const
{
std::map<Item, int> bill = items;
for(auto& rule: rules)
bill = rule->applyDiscount(bill);
return bill;
}
double total(const std::map<Item, int>& bill) const
{
double sum = 0;
for(auto& kv: bill)
sum += kv.first.price * kv.second;
return sum;
}
double total() const
{
return total(getBill());
}
};
int main()
{
std::string line;
while(getline(std::cin, line))
{
std::string entry;
std::stringstream ss(line);
ShoppingCart cart;
while(ss >> entry)
{
cart.addItem(tours.at(entry));
}
cart.addRule(std::unique_ptr<Rule>(new BridgeClimbDiscountRule));
cart.addRule(std::unique_ptr<Rule>(new ThreeForTwoRule));
cart.addRule(std::unique_ptr<Rule>(new FreeSkyTourRule));
std::cout << line << std::endl;
auto bill = cart.getBill();
for(auto& kv: bill)
std::cout << "\tx" << kv.second << " : " << kv.first << std::endl;
std::cout << "\tTotal in $ : " << cart.total(bill) << std::endl;
std::cout << std::endl;
}
return 0;
}