/*
 * Copyright (C) 2008-2012 Robert Ancell
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
 * license.
 */

[GtkTemplate (ui = "/org/gnome/calculator/math-converter.ui")]
public class MathConverter : Gtk.Grid
{
    private MathEquation equation = null;

    private string category;

    [GtkChild]
    private Gtk.CellRendererText from_renderer;

    [GtkChild]
    private Gtk.ComboBox from_combo;
    [GtkChild]
    private Gtk.ComboBox to_combo;
    [GtkChild]
    private Gtk.Label from_label;
    [GtkChild]
    private Gtk.Label to_label;

    public signal void changed ();

    construct
    {
        from_combo.set_cell_data_func (from_renderer, from_cell_data_func);
        CurrencyManager.get_default ().updated.connect (() => { update_result_label (); });

        update_from_model ();
    }

    public MathConverter (MathEquation equation)
    {
      set_equation (equation);
    }

    public void set_equation (MathEquation equation)
    {
        this.equation = equation;
        equation.notify["display"].connect ((pspec) => { update_result_label (); });
    }

    public void set_category (string? category)
    {
        if (this.category == category)
            return;
        this.category = category;

        update_from_model ();
    }

    public string get_category ()
    {
        return category;
    }

    public void set_conversion (/*string category,*/ string unit_a, string unit_b)
    {
        var ua = UnitManager.get_default ().get_unit_by_name (unit_a);
        var ub = UnitManager.get_default ().get_unit_by_name (unit_b);
        if (ua == null || ub == null)
        {
            /* Select the first unit */
            var model = from_combo.get_model ();
            Gtk.TreeIter iter;
            if (model.get_iter_first (out iter))
            {
                Gtk.TreeIter child_iter;
                while (model.iter_children (out child_iter, iter))
                    iter = child_iter;
                from_combo.set_active_iter (iter);
            }
            return;
        }

        set_active_unit (from_combo, null, ua);
        set_active_unit (to_combo, null, ub);
    }

    public void get_conversion (out Unit from_unit, out Unit to_unit)
    {
        Gtk.TreeIter from_iter, to_iter;

        from_combo.get_active_iter (out from_iter);
        to_combo.get_active_iter (out to_iter);

        from_combo.get_model ().get (from_iter, 2, out from_unit, -1);
        to_combo.get_model ().get (to_iter, 2, out to_unit, -1);
    }

    private void update_result_label ()
    {
        var x = equation.number;
        if (x == null)
            return;

        Unit source_unit, target_unit;
        var z = convert_equation (x, out source_unit, out target_unit);
        if (z != null)
        {
            var source_text = source_unit.format (x);
            var target_text = target_unit.format (z);
            from_label.set_text (source_text);
            to_label.set_text (target_text);
        }
    }

    private void update_from_model ()
    {
        var from_model = new Gtk.TreeStore (3, typeof (string), typeof (UnitCategory), typeof (Unit));

        if (category == null)
        {
            var categories = UnitManager.get_default ().get_categories ();
            foreach (var category in categories)
            {
                Gtk.TreeIter parent;
                from_model.append (out parent, null);
                from_model.set (parent, 0, category.display_name, 1, category, -1);

                foreach (var unit in category.get_units ())
                {
                    Gtk.TreeIter iter;
                    from_model.append (out iter, parent);
                    from_model.set (iter, 0, unit.display_name, 1, category, 2, unit, -1);
                }
            }
        }
        else
        {
            var c = UnitManager.get_default ().get_category (category);
            foreach (var unit in c.get_units ())
            {
                Gtk.TreeIter iter;
                from_model.append (out iter, null);
                from_model.set (iter, 0, unit.display_name, 1, c, 2, unit, -1);
            }
        }

        from_combo.model = from_model;
    }

    private bool iter_is_unit (Gtk.TreeModel model, Gtk.TreeIter iter, Unit unit)
    {
        Unit u;
        model.get (iter, 2, out u, -1);
        return u == unit;
    }

    private bool set_active_unit (Gtk.ComboBox combo, Gtk.TreeIter? iter, Unit unit)
    {
        var model = combo.get_model ();
        if (iter != null && iter_is_unit (model, iter, unit))
        {
            combo.set_active_iter (iter);
            return true;
        }

        Gtk.TreeIter child_iter;
        if (!model.iter_children (out child_iter, iter))
            return false;

        do
        {
            if (set_active_unit (combo, child_iter, unit))
                return true;
        } while (model.iter_next (ref child_iter));

        return false;
    }

    [GtkCallback]
    private void from_combobox_changed_cb ()
    {
        UnitCategory? category = null, to_category = null;
        Unit unit;
        Gtk.TreeIter iter, to_iter;

        var model = from_combo.get_model ();
        var to_model = to_combo.get_model () as Gtk.ListStore;

        if (!from_combo.get_active_iter (out iter))
            return;

        model.get (iter, 1, out category, 2, out unit, -1);
        if (to_combo.get_active_iter (out to_iter))
            to_model.get (to_iter, 1, out to_category);

        if (category != to_category)
        {
            /* Set the to combobox to be the list of units can be converted to */
            to_model = new Gtk.ListStore (3, typeof (string), typeof (UnitCategory), typeof (Unit));
            foreach (var u in category.get_units ())
            {
                to_model.append (out iter);
                to_model.set (iter, 0, u.display_name, 1, category, 2, u, -1);
            }
            to_combo.model = to_model;

            /* Select the first possible unit */
            to_combo.set_active (0);
        }
    }

    [GtkCallback]
    private void to_combobox_changed_cb ()
    {
        /* Conversion must have changed */
        update_result_label ();
        changed ();
    }

    private void from_cell_data_func (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel tree_model, Gtk.TreeIter iter)
    {
        cell.set ("sensitive", !tree_model.iter_has_child (iter));
    }

    [GtkCallback]
    private void swap_button_clicked_cb ()
    {
        Unit? from_unit, to_unit;
        get_conversion (out from_unit, out to_unit);

        set_active_unit (from_combo, null, to_unit);
        set_active_unit (to_combo, null, from_unit);

        do_convert(out from_unit, out to_unit);

        update_result_label ();
    }

    private void do_convert (out Unit? from_unit, out Unit? to_unit) {
        var x = equation.number;
        if (x != null)
        {
            var z = convert_equation (x, out from_unit, out to_unit);
            if (z != null && from_unit != null && to_unit != null)
            {
                equation.set ("%s %s %s %s".printf(equation.serializer.to_string (x), from_unit.name, _("in"), to_unit.name));
                equation.solve ();
            }
        }
    }

    [GtkCallback]
    private void convert_button_clicked_cb ()
    {
        Unit? from_unit, to_unit;
        do_convert (out from_unit, out to_unit);

        update_result_label ();
    }

    private Number?  convert_equation (Number x,
                                       out Unit? source_unit,
                                       out Unit? target_unit)
    {
        Gtk.TreeIter from_iter, to_iter;
        if (!from_combo.get_active_iter (out from_iter))
            return null;
        if (!to_combo.get_active_iter (out to_iter))
            return null;
        UnitCategory category;
        from_combo.model.get (from_iter, 1, out category, 2, out source_unit, -1);
        to_combo.model.get (to_iter, 2, out target_unit, -1);

        return category.convert (x, source_unit, target_unit);
  }
}
