#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# 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.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Copyright 2012 Thomi Richards

from gi.repository import Gtk
from gi.repository import AppIndicator3
import logging
logging.basicConfig(level=logging.DEBUG,
        format="%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s")

from model import JenkinsServerModel
from menu import JenkinsJobMenuItem
from settings_ui import SettingsDialog
from settings import save_settings, get_settings
from graphics import system_has_image
from server_monitor import get_server_monitor
import notifications

class JenkinsIndicator(object):
    """The main indicator class."""

    def __init__(self):
        self.create_server_models()
        self.create_indicator()
        self.create_menu_entries()
        self.set_indicator_menu()

    def create_server_models(self):
        """Make the server models dictionary."""
        self.server_models = {}
        servers_from_settings = get_settings().get_all_servers()
        for sname, sdict in servers_from_settings.iteritems():
            server_model = JenkinsServerModel(sname, sdict)
            server_model.connect("job-added", self.on_new_job_found)
            self.server_models[sname] = server_model

    def create_indicator(self):
        """Make the app indicator object."""
        # if we're running from source we can't use the jenkins image, so
        # fallback to some crappy system icon that's probably going to be
        # installed:
        if system_has_image("jenkins"):
            icon_name = "jenkins"
        else:
            icon_name = "gnome-system"

        self.ind = AppIndicator3.Indicator.new ("indicator-jenkins",
                                        icon_name,
                                        AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
        self.ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
        self.ind.set_title("Jenkins")

        if system_has_image("jenkins-alert"):
            alert_icon_name = "jenkins-alert"
        else:
            alert_icon_name = icon_name
        self.ind.set_attention_icon_full(alert_icon_name, "Jenkins has builds failing")

    def create_menu_entries(self):
        """Create the menu that is shown under the app indicator."""
        self.menu = Gtk.Menu()

        self.create_jenkins_job_menu_entries()
        self.menu.append(Gtk.SeparatorMenuItem())
        self.create_settings_and_quit_menu_entries()
        #self.menu.popup(None, None, None, 2, 0)

    def create_settings_and_quit_menu_entries(self):
        mi_settings = Gtk.MenuItem("Settings")
        mi_settings.connect("activate", self.on_settings)
        self.menu.append(mi_settings)

        mi_quit = Gtk.MenuItem("Quit")
        mi_quit.connect("activate", self.on_quit)
        self.menu.append(mi_quit)

        self.menu.show_all()

    def create_jenkins_job_menu_entries(self):
        """Create menu entries for each jenkins job we're tracking.

        We do this on startup so the user sees the menu entries straight away,
        and does not have to wait for a full data retreval.

        """
        for name, server in get_settings().get_all_servers().iteritems():
            for job_name in server['monitor_jobs']:
                logging.debug("Creating jenkins job menu item for %s.", job_name)
                menu_item = self.create_menu_item_for_job(job_name)
                self.insert_jenkins_menuitem(menu_item)

    def create_menu_item_for_job(self, job_name):
        """Create a menu item for a jenkins job."""
        menu_item = JenkinsJobMenuItem(job_name)
        menu_item.set_state_color('grey')
        return menu_item

    def insert_jenkins_menuitem(self, menu_item):
        """Insert the menu item at the correct position.

        We want to keep the indicator items in alphabetical order.
        """
        if not self.menu.get_children():
            logging.debug("Menu has no children, appending menu item '%s'", menu_item.get_label())
            self.menu.append(menu_item)
            menu_item.show()
        else:
            for pos, child in enumerate(self.menu.get_children()):
                if type(child) is Gtk.SeparatorMenuItem:
                    # insert here, we've hit the end of the job list:
                    logging.debug("Inserting menu item '%s' at pos %d since next item is a separator.", menu_item.get_label(), pos)
                    self.menu.insert(menu_item, pos)
                    menu_item.show()
                    return
                if child.get_label() > menu_item.get_label():
                    # insert here, the next item in the menu comes after us:
                    logging.debug("Inserting menu item '%s' at pos %d since next item is greater than us.", menu_item.get_label(), pos)
                    self.menu.insert(menu_item, pos)
                    menu_item.show()
                    return
            # ran out of places - append to menu:
            self.menu.append(menu_item)
            menu_item.show()

    def set_indicator_menu(self):
        self.ind.set_menu(self.menu)

    def on_new_job_found(self, sender, new_job):
        """Callback will be called when the server finds a new job."""
        if new_job.monitored:
            menu_entry = self.get_jenkins_menu_entry_by_name(new_job.name)
            if not menu_entry:
                menu_entry = JenkinsJobMenuItem(new_job.name)
                self.insert_jenkins_menuitem(menu_entry)
                menu_entry.show()
            menu_entry.attach_job_model(new_job)
        # The notifications framework filters on 'job.monitored' as well, so
        # we connect every job here:
        new_job.connect("notify::build", notifications.send_job_update_notification)
        new_job.connect("notify::monitored", self.on_job_changed)
        new_job.connect_after("notify::color", self.maybe_update_indicator_icon)
        self.maybe_update_indicator_icon()

    def on_job_changed(self, job, prop):
        """Called whenever *any* CI job changes it's 'monitored' state."""
        if job.monitored:
            menu_entry = self.get_jenkins_menu_entry_by_name(job.name)
            if not menu_entry:
                menu_entry = JenkinsJobMenuItem(job.name)
                self.insert_jenkins_menuitem(menu_entry)
                menu_entry.show()
            menu_entry.attach_job_model(job)
        else:
            menu_entry = self.get_jenkins_menu_entry_by_name(job.name)
            if menu_entry:
                self.menu.remove(menu_entry)
        self.maybe_update_indicator_icon()

    def maybe_update_indicator_icon(self, *args):
        """Update the indicator icon if we have jobs that have failed."""
        status = AppIndicator3.IndicatorStatus.ACTIVE
        for model in self.server_models.values():
            for job in model.get_jobs():
                if job.monitored and job.color in ('yellow', 'red'):
                   status = AppIndicator3.IndicatorStatus.ATTENTION
        self.ind.set_status(status)

    def get_jenkins_menu_entry_by_name(self, name):
        """Returns a JenkinsJobMenuItem that's in our menu with the given name,
        or None.
        """
        for child in self.menu.get_children():
            if type(child) is JenkinsJobMenuItem and child.get_label() == name:
                return child
        return None

    def on_quit(self, sender):
        get_server_monitor().stop()
        for name, model in self.server_models.iteritems():
            get_settings().update_settings_from_model(model)
        Gtk.main_quit()

    def on_settings(self, sender):
        dialog = SettingsDialog(self.server_models['default'])
        response = dialog.run()
        dialog.destroy()
        if response == Gtk.ResponseType.REJECT:
            logging.debug("User rejected changes.")
        elif response == Gtk.ResponseType.OK:
            logging.debug("User accepted changes.")
            save_settings()


if __name__ == "__main__":
    try:
        notifications.start()
        get_server_monitor() # TODO - this is here so the WP spins up early.
        ji = JenkinsIndicator()
        Gtk.main()
        notifications.stop()
    finally:
        save_settings()

