/** @file
 * @brief Memory statistics dialog
 */
/* Authors:
 *   MenTaLguY <mental@rydia.net>
 *
 * Copyright (C) 2005 
 *
 * Released under GNU GPL.  Read the file 'COPYING' for more information.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <glibmm/i18n.h>
#include <gtkmm/liststore.h>
#include <gtkmm/treeview.h>

#include "gc-core.h"
#include "ui/dialog/memory.h"
#include "debug/heap.h"
#include "verbs.h"

namespace Inkscape {
namespace UI {
namespace Dialog {

namespace {

Glib::ustring format_size(std::size_t value) {
    if (!value) {
        return Glib::ustring("0");
    }

    typedef std::vector<char> Digits;
    typedef std::vector<Digits *> Groups;

    Groups groups;

    Digits *digits;

    while (value) {
        unsigned places=3;
        digits = new Digits();
        digits->reserve(places);

        while ( value && places ) {
            digits->push_back('0' + (char)( value % 10 ));
            value /= 10;
            --places;
        }

        groups.push_back(digits);
    }

    Glib::ustring temp;

    while (true) {
        digits = groups.back();
        while (!digits->empty()) {
            temp.append(1, digits->back());
            digits->pop_back();
        }
        delete digits;

        groups.pop_back();
        if (groups.empty()) {
            break;
        }

        temp.append(",");
    }

    return temp;
}

}

struct Memory::Private {
    class ModelColumns : public Gtk::TreeModel::ColumnRecord {
    public:
        Gtk::TreeModelColumn<Glib::ustring> name;
        Gtk::TreeModelColumn<Glib::ustring> used;
        Gtk::TreeModelColumn<Glib::ustring> slack;
        Gtk::TreeModelColumn<Glib::ustring> total;

        ModelColumns() { add(name); add(used); add(slack); add(total); }
    };

    Private() {
        model = Gtk::ListStore::create(columns);
        view.set_model(model);
        view.append_column(_("Heap"), columns.name);
        view.append_column(_("In Use"), columns.used);
        // TRANSLATORS: "Slack" refers to memory which is in the heap but currently unused.
        //  More typical usage is to call this memory "free" rather than "slack".
        view.append_column(_("Slack"), columns.slack);
        view.append_column(_("Total"), columns.total);
    }

    void update();

    void start_update_task();
    void stop_update_task();

    ModelColumns columns;
    Glib::RefPtr<Gtk::ListStore> model;
    Gtk::TreeView view;

    sigc::connection update_task;
};

void Memory::Private::update() {
    Debug::Heap::Stats total = { 0, 0 };

    int aggregate_features = Debug::Heap::SIZE_AVAILABLE | Debug::Heap::USED_AVAILABLE;
    Gtk::ListStore::iterator row;

    row = model->children().begin();

    for ( unsigned i = 0 ; i < Debug::heap_count() ; i++ ) {
        Debug::Heap *heap=Debug::get_heap(i);
        if (heap) {
            Debug::Heap::Stats stats=heap->stats();
            int features=heap->features();

            aggregate_features &= features;

            if ( row == model->children().end() ) {
                row = model->append();
            }

            row->set_value(columns.name, Glib::ustring(heap->name()));
            if ( features & Debug::Heap::SIZE_AVAILABLE ) {
                row->set_value(columns.total, format_size(stats.size));
                total.size += stats.size;
            } else {
                row->set_value(columns.total, Glib::ustring(_("Unknown")));
            }
            if ( features & Debug::Heap::USED_AVAILABLE ) {
                row->set_value(columns.used, format_size(stats.bytes_used));
                total.bytes_used += stats.bytes_used;
            } else {
                row->set_value(columns.used, Glib::ustring(_("Unknown")));
            }
            if ( features & Debug::Heap::SIZE_AVAILABLE &&
                 features & Debug::Heap::USED_AVAILABLE )
            {
                row->set_value(columns.slack, format_size(stats.size - stats.bytes_used));
            } else {
                row->set_value(columns.slack, Glib::ustring(_("Unknown")));
            }

            ++row;
        }
    }

    if ( row == model->children().end() ) {
        row = model->append();
    }

    Glib::ustring value;

    row->set_value(columns.name, Glib::ustring(_("Combined")));

    if ( aggregate_features & Debug::Heap::SIZE_AVAILABLE ) {
        row->set_value(columns.total, format_size(total.size));
    } else {
        row->set_value(columns.total, Glib::ustring("> ") + format_size(total.size));
    }

    if ( aggregate_features & Debug::Heap::USED_AVAILABLE ) {
        row->set_value(columns.used, format_size(total.bytes_used));
    } else {
        row->set_value(columns.used, Glib::ustring("> ") + format_size(total.bytes_used));
    }

    if ( aggregate_features & Debug::Heap::SIZE_AVAILABLE &&
         aggregate_features & Debug::Heap::USED_AVAILABLE )
    {
        row->set_value(columns.slack, format_size(total.size - total.bytes_used));
    } else {
        row->set_value(columns.slack, Glib::ustring(_("Unknown")));
    }

    ++row;

    while ( row != model->children().end() ) {
        row = model->erase(row);
    }
}

void Memory::Private::start_update_task() {
    update_task.disconnect();
    update_task = Glib::signal_timeout().connect(
        sigc::bind_return(sigc::mem_fun(*this, &Private::update), true),
        500
    );
}

void Memory::Private::stop_update_task() {
    update_task.disconnect();
}

Memory::Memory() 
    : UI::Widget::Panel ("", "/dialogs/memory", SP_VERB_HELP_MEMORY, _("Recalculate")),
      _private(*(new Memory::Private())) 
{
    _getContents()->add(_private.view);

    _private.update();

    show_all_children();

    signal_show().connect(sigc::mem_fun(_private, &Private::start_update_task));
    signal_hide().connect(sigc::mem_fun(_private, &Private::stop_update_task));

    _private.start_update_task();
}

Memory::~Memory() {
    delete &_private;
}

void Memory::_apply() {
    GC::Core::gcollect();
    _private.update();
}

} // namespace Dialog
} // namespace UI
} // namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
