############################################################
# FlatCAM: 2D Post-processing for Manufacturing            #
# http://caram.cl/software/flatcam                         #
# Author: Juan Pablo Caram (c)                             #
# Date: 4/20/2014                                          #
# MIT Licence                                              #
############################################################

import inspect  # TODO: Remove

from gi.repository import Gtk, GdkPixbuf, GLib

from FlatCAMObj import *
from FlatCAM_GTK import FlatCAMApp


class ObjectCollection:

    classdict = {
        "gerber": FlatCAMGerber,
        "excellon": FlatCAMExcellon,
        "cncjob": FlatCAMCNCjob,
        "geometry": FlatCAMGeometry
    }

    icon_files = {
        "gerber": "share/flatcam_icon16.png",
        "excellon": "share/drill16.png",
        "cncjob": "share/cnc16.png",
        "geometry": "share/geometry16.png"
    }

    def __init__(self):

        ### Icons for the list view
        self.icons = {}
        for kind in ObjectCollection.icon_files:
            self.icons[kind] = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icon_files[kind])

        ### GUI List components
        ## Model
        self.store = Gtk.ListStore(FlatCAMObj)

        ## View
        self.view = Gtk.TreeView(model=self.store)
        #self.view.connect("row_activated", self.on_row_activated)
        self.tree_selection = self.view.get_selection()
        self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change)

        ## Renderers
        # Icon
        renderer_pixbuf = Gtk.CellRendererPixbuf()
        column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf)

        def _set_cell_icon(column, cell, model, it, data):
            obj = model.get_value(it, 0)
            cell.set_property('pixbuf', self.icons[obj.kind])

        column_pixbuf.set_cell_data_func(renderer_pixbuf, _set_cell_icon)
        self.view.append_column(column_pixbuf)

        # Name
        renderer_text = Gtk.CellRendererText()
        column_text = Gtk.TreeViewColumn("Name", renderer_text)

        def _set_cell_text(column, cell, model, it, data):
            obj = model.get_value(it, 0)
            cell.set_property('text', obj.options["name"])

        column_text.set_cell_data_func(renderer_text, _set_cell_text)
        self.view.append_column(column_text)

    def print_list(self):
        iterat = self.store.get_iter_first()
        while iterat is not None:
            obj = self.store[iterat][0]
            print obj
            iterat = self.store.iter_next(iterat)

    def delete_all(self):
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
        self.store.clear()

    def delete_active(self):
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_active()")
        try:
            model, treeiter = self.tree_selection.get_selected()
            self.store.remove(treeiter)
        except:
            pass

    def on_list_selection_change(self, selection):
        """
        Callback for change in selection on the objects' list.
        Instructs the new selection to build the UI for its options.

        :param selection: Ignored.
        :return: None
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.on_list_selection_change()")

        active = self.get_active()
        active.build_ui()

    def set_active(self, name):
        """
        Sets an object as the active object in the program. Same
        as `set_list_selection()`.

        :param name: Name of the object.
        :type name: str
        :return: None
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_active()")
        self.set_list_selection(name)

    def get_active(self):
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_active()")
        try:
            model, treeiter = self.tree_selection.get_selected()
            return model[treeiter][0]
        except (TypeError, ValueError):
            return None

    def set_list_selection(self, name):
        """
        Sets which object should be selected in the list.

        :param name: Name of the object.
        :rtype name: str
        :return: None
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_list_selection()")
        iterat = self.store.get_iter_first()
        while iterat is not None and self.store[iterat][0].options["name"] != name:
            iterat = self.store.iter_next(iterat)
        self.tree_selection.select_iter(iterat)

    def append(self, obj, active=False):
        """
        Add a FlatCAMObj the the collection. This method is thread-safe.

        :param obj: FlatCAMObj to append
        :type obj: FlatCAMObj
        :param active: If it is to become the active object after appending
        :type active: bool
        :return: None
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.append()")

        def guitask():
            self.store.append([obj])
            if active:
                self.set_list_selection(obj.options["name"])
        GLib.idle_add(guitask)

    def get_names(self):
        """
        Gets a list of the names of all objects in the collection.

        :return: List of names.
        :rtype: list
        """

        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_names()")

        names = []
        iterat = self.store.get_iter_first()
        while iterat is not None:
            obj = self.store[iterat][0]
            names.append(obj.options["name"])
            iterat = self.store.iter_next(iterat)
        return names

    def get_bounds(self):
        """
        Finds coordinates bounding all objects in the collection.

        :return: [xmin, ymin, xmax, ymax]
        :rtype: list
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")

        # TODO: Move the operation out of here.

        xmin = Inf
        ymin = Inf
        xmax = -Inf
        ymax = -Inf

        iterat = self.store.get_iter_first()
        while iterat is not None:
            obj = self.store[iterat][0]
            try:
                gxmin, gymin, gxmax, gymax = obj.bounds()
                xmin = min([xmin, gxmin])
                ymin = min([ymin, gymin])
                xmax = max([xmax, gxmax])
                ymax = max([ymax, gymax])
            except:
                FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry.")
            iterat = self.store.iter_next(iterat)
        return [xmin, ymin, xmax, ymax]

    def get_list(self):
        """
        Returns a list with all FlatCAMObj.

        :return: List with all FlatCAMObj.
        :rtype: list
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_list()")
        collection_list = []
        iterat = self.store.get_iter_first()
        while iterat is not None:
            obj = self.store[iterat][0]
            collection_list.append(obj)
            iterat = self.store.iter_next(iterat)
        return collection_list

    def get_by_name(self, name):
        """
        Fetches the FlatCAMObj with the given `name`.

        :param name: The name of the object.
        :type name: str
        :return: The requested object or None if no such object.
        :rtype: FlatCAMObj or None
        """
        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")

        iterat = self.store.get_iter_first()
        while iterat is not None:
            obj = self.store[iterat][0]
            if obj.options["name"] == name:
                return obj
            iterat = self.store.iter_next(iterat)
        return None

    # def change_name(self, old_name, new_name):
    #     """
    #     Changes the name of `FlatCAMObj` named `old_name` to `new_name`.
    #
    #     :param old_name: Name of the object to change.
    #     :type old_name: str
    #     :param new_name: New name.
    #     :type new_name: str
    #     :return: True if name change succeeded, False otherwise. Will fail
    #        if no object with `old_name` is found.
    #     :rtype: bool
    #     """
    #     print inspect.stack()[1][3], "--> OC.change_name()"
    #     iterat = self.store.get_iter_first()
    #     while iterat is not None:
    #         obj = self.store[iterat][0]
    #         if obj.options["name"] == old_name:
    #             obj.options["name"] = new_name
    #             self.store.row_changed(0, iterat)
    #             return True
    #         iterat = self.store.iter_next(iterat)
    #     return False