#include <iostream>
#include <stdexcept>
#include <sstream>

#include <libglademm/xml.h>
#include <gtkmm.h>

class CalcWin : public sigc::trackable
{
public:
	explicit CalcWin(Glib::RefPtr<Gnome::Glade::Xml> refXml);
	~CalcWin();

	Gtk::Window&  get_window() const { return *calcwin_; }
	void          button_pressed(char b);
	void          clear_pressed();
	Glib::ustring eval() const;

private:
	void          link_button(Glib::RefPtr<Gnome::Glade::Xml> refXml, std::string name, char value);
	Gtk::Window*  calcwin_;
	Gtk::Entry*   display_;

	Glib::ustring accumulator_;
	Glib::ustring value_;
	char          op_;
	bool          lastequals_;
	bool          doclear_;
};

CalcWin::
CalcWin(Glib::RefPtr<Gnome::Glade::Xml> refXml)
	: calcwin_(0)
	, display_(0)
	, accumulator_("0")
	, value_("0")
	, op_('+')
	, lastequals_(false)
	, doclear_(true)
{
	refXml->get_widget("calcwindow", calcwin_);
	if (!calcwin_)
		throw std::runtime_error("Couldn't find calcwin in calc.glade");
	refXml->get_widget("display", display_);
	if (!display_)
		throw std::runtime_error("Couldn't find display in calc.glade");
	display_->set_alignment(Gtk::ALIGN_RIGHT);
	display_->set_text("0");

	Gtk::Button* but = 0;
	refXml->get_widget("btnclear", but);
	if (!but)
		throw std::runtime_error("Couldn't find clear button in calc.glade");
	but->signal_clicked().connect(sigc::mem_fun(*this, &CalcWin::clear_pressed));

	for (int c=0; c<10; ++c)
	{
		std::ostringstream name;
		name << "btn" << c;
		link_button(refXml, name.str(), name.str()[3]);
	}
	link_button(refXml, "btnplus", '+');
	link_button(refXml, "btnminus", '-');
	link_button(refXml, "btndiv", '/');
	link_button(refXml, "btnmul", '*');
	link_button(refXml, "btnequals", '=');
}

void CalcWin::
link_button(Glib::RefPtr<Gnome::Glade::Xml> refXml, std::string name, char value)
{
	Gtk::Button* but = 0;
	refXml->get_widget(name, but);
	if (!but)
		throw std::runtime_error("Couldn't find button in calc.glade");
	but->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &CalcWin::button_pressed), value));
}

CalcWin::
~CalcWin()
{
	delete calcwin_;
}

void CalcWin::
clear_pressed()
{
	accumulator_ = "0";
	value_ = "0";
	op_ = '+';
	display_->set_text("0");
}

void CalcWin::
button_pressed(char b)
{
	Glib::ustring current(display_->get_text());
	switch (b)
	{
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		if (doclear_)
		{
			current = "";
			doclear_ = false;
		}
		display_->set_text(current+b);
		lastequals_ = false;
		break;
	case '+': case '-': case '/': case '*':
		value_ = current;
		if (b == op_)
			accumulator_ = eval();
		else
			accumulator_ = current;
		display_->set_text(accumulator_);
		op_ = b;
		doclear_ = true;
		lastequals_ = false;
		break;
	case '=':
		if (!lastequals_)
			value_ = current;
		accumulator_ = eval();
		display_->set_text(accumulator_);
		lastequals_ = true;
		break;
	}
}

Glib::ustring CalcWin::
eval() const
{
	double val=0, acc=0;
	std::stringstream convval;
	std::stringstream convacc;

	convval << value_;
	convval >> val;

	convacc << accumulator_;
	convacc >> acc;

	switch (op_)
	{
	case '+':
		acc += val;
		break;
	case '-':
		acc -= val;
		break;
	case '/':
		acc /= val;
		break;
	case '*':
		acc *= val;
		break;
	}
	std::ostringstream oconv;
	oconv << acc;
	Glib::ustring result(oconv.str());
	return result;
}

int main(int argc, char* argv[])
{
	try
	{
		Gtk::Main kit(argc, argv);
		Glib::RefPtr<Gnome::Glade::Xml> refXml = Gnome::Glade::Xml::create("calc.glade");
		CalcWin calc(refXml);
		kit.run(calc.get_window());
		return 0;
	}
	catch (std::exception const& ex)
	{
		std::cerr << "Error: " << ex.what() << std::endl;
		return 1;
	}
}
