#!/usr/bin/env python
#
# BitBake Graphical GTK User Interface
#
# Copyright (C) 2012        Intel Corporation
#
# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
# Authored by Shane Wang <shane.wang@intel.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import gobject
import gtk
from bb.ui.crumbs.hobcolor import HobColors
from bb.ui.crumbs.hobwidget import hic, HobViewTable, HobAltButton, HobButton
from bb.ui.crumbs.hobpages import HobPage
import subprocess
from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog

#
# ImageDetailsPage
#
class ImageDetailsPage (HobPage):

    class DetailBox (gtk.EventBox):
        def __init__(self, widget = None, varlist = None, vallist = None, icon = None, button = None, button2=None, color = HobColors.LIGHT_GRAY):
            gtk.EventBox.__init__(self)

            # set color
            style = self.get_style().copy()
            style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
            self.set_style(style)

            self.row = gtk.Table(1, 2, False)
            self.row.set_border_width(10)
            self.add(self.row)
        
            total_rows = 0
            if widget:
                total_rows = 10
            if varlist and vallist:
                # pack the icon and the text on the left
                total_rows += len(varlist)
            self.table = gtk.Table(total_rows, 20, True)
            self.table.set_row_spacings(6)
            self.table.set_size_request(100, -1)
            self.row.attach(self.table, 0, 1, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL)
            
            colid = 0
            rowid = 0
            self.line_widgets = {}
            if icon:
                self.table.attach(icon, colid, colid + 2, 0, 1)
                colid = colid + 2
            if widget:
                self.table.attach(widget, colid, 20, 0, 10)
                rowid = 10
            if varlist and vallist:
                for row in range(rowid, total_rows):
                    index = row - rowid
                    self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
                    self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
            # pack the button on the right
            if button:
                self.bbox = gtk.VBox()
                self.bbox.pack_start(button, expand=True, fill=False)
                if button2:
                    self.bbox.pack_start(button2, expand=True, fill=False)
                self.bbox.set_size_request(150,-1)
                self.row.attach(self.bbox, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.EXPAND)
                
        def update_line_widgets(self, variable, value):
            if len(self.line_widgets) == 0:
                return
            if not isinstance(self.line_widgets[variable], gtk.Label):
                return
            self.line_widgets[variable].set_markup(self.format_line(variable, value))

        def wrap_line(self, inputs):
            # wrap the long text of inputs
            wrap_width_chars = 75
            outputs = ""
            tmps = inputs
            less_chars = len(inputs)
            while (less_chars - wrap_width_chars) > 0:
                less_chars -= wrap_width_chars
                outputs += tmps[:wrap_width_chars] + "\n               "
                tmps = inputs[less_chars:]
            outputs += tmps
            return outputs

        def format_line(self, variable, value):
            wraped_value = self.wrap_line(value)
            markup = "<span weight=\'bold\'>%s</span>" % variable
            markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
            return markup

        def text2label(self, variable, value):
            # append the name:value to the left box
            # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
            label = gtk.Label()
            label.set_alignment(0.0, 0.5)
            label.set_markup(self.format_line(variable, value))
            return label

    class BuildDetailBox (gtk.EventBox):
        def __init__(self, varlist = None, vallist = None, icon = None, color = HobColors.LIGHT_GRAY):
            gtk.EventBox.__init__(self)

            # set color
            style = self.get_style().copy()
            style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
            self.set_style(style)

            self.hbox = gtk.HBox()
            self.hbox.set_border_width(10)
            self.add(self.hbox)

            total_rows = 0
            if varlist and vallist:
                # pack the icon and the text on the left
                total_rows += len(varlist)
            self.table = gtk.Table(total_rows, 20, True)
            self.table.set_row_spacings(6)
            self.table.set_size_request(100, -1)
            self.hbox.pack_start(self.table, expand=True, fill=True, padding=15)

            colid = 0
            rowid = 0
            self.line_widgets = {}
            if icon:
                self.table.attach(icon, colid, colid + 2, 0, 1)
                colid = colid + 2
            if varlist and vallist:
                for row in range(rowid, total_rows):
                    index = row - rowid
                    self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
                    self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
                
        def update_line_widgets(self, variable, value):
            if len(self.line_widgets) == 0:
                return
            if not isinstance(self.line_widgets[variable], gtk.Label):
                return
            self.line_widgets[variable].set_markup(self.format_line(variable, value))

        def wrap_line(self, inputs):
            # wrap the long text of inputs
            wrap_width_chars = 75
            outputs = ""
            tmps = inputs
            less_chars = len(inputs)
            while (less_chars - wrap_width_chars) > 0:
                less_chars -= wrap_width_chars
                outputs += tmps[:wrap_width_chars] + "\n               "
                tmps = inputs[less_chars:]
            outputs += tmps
            return outputs

        def format_line(self, variable, value):
            wraped_value = self.wrap_line(value)
            markup = "<span weight=\'bold\'>%s</span>" % variable
            markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
            return markup

        def text2label(self, variable, value):
            # append the name:value to the left box
            # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
            label = gtk.Label()
            label.set_alignment(0.0, 0.5)
            label.set_markup(self.format_line(variable, value))
            return label

    def __init__(self, builder):
        super(ImageDetailsPage, self).__init__(builder, "Image details")

        self.image_store = []
        self.button_ids = {}
        self.details_bottom_buttons = gtk.HBox(False, 6)
        self.create_visual_elements()

    def create_visual_elements(self):
        # create visual elements
        # create the toolbar
        self.toolbar = gtk.Toolbar()
        self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
        self.toolbar.set_style(gtk.TOOLBAR_BOTH)

        my_images_button = self.append_toolbar_button(self.toolbar,
            "Images",
            hic.ICON_IMAGES_DISPLAY_FILE,
            hic.ICON_IMAGES_HOVER_FILE,
            "Open previously built images",
            self.my_images_button_clicked_cb)
        settings_button = self.append_toolbar_button(self.toolbar,
            "Settings",
            hic.ICON_SETTINGS_DISPLAY_FILE,
            hic.ICON_SETTINGS_HOVER_FILE,
            "View additional build settings",
            self.settings_button_clicked_cb)

        self.details_top_buttons = self.add_onto_top_bar(self.toolbar)

    def _remove_all_widget(self):
        children = self.get_children() or []
        for child in children:
            self.remove(child)
        children = self.box_group_area.get_children() or []
        for child in children:
            self.box_group_area.remove(child)
        children = self.details_bottom_buttons.get_children() or []
        for child in children:
            self.details_bottom_buttons.remove(child)

    def show_page(self, step):
        self.build_succeeded = (step == self.builder.IMAGE_GENERATED)
        image_addr = self.builder.parameters.image_addr
        image_names = self.builder.parameters.image_names
        if self.build_succeeded:
            machine = self.builder.configuration.curr_mach
            base_image = self.builder.recipe_model.get_selected_image()
            layers = self.builder.configuration.layers
            pkg_num = "%s" % len(self.builder.package_model.get_selected_packages())
            log_file = self.builder.current_logfile
        else:
            pkg_num = "N/A"
            log_file = None

        # remove
        for button_id, button in self.button_ids.items():
            button.disconnect(button_id)
        self._remove_all_widget()

        # repack
        self.pack_start(self.details_top_buttons, expand=False, fill=False)
        self.pack_start(self.group_align, expand=True, fill=True)

        self.build_result = None
        if self.build_succeeded and self.builder.current_step == self.builder.IMAGE_GENERATING:
            # building is the previous step
            icon = gtk.Image()
            pixmap_path = hic.ICON_INDI_CONFIRM_FILE
            color = HobColors.RUNNING
            pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path)
            icon.set_from_pixbuf(pix_buffer)
            varlist = [""]
            vallist = ["Your image is ready"]
            self.build_result = self.BuildDetailBox(varlist=varlist, vallist=vallist, icon=icon, color=color)
            self.box_group_area.pack_start(self.build_result, expand=False, fill=False)

        self.buttonlist = ["Build new image", "Run image", "Deploy image"]

        # Name
        self.image_store = []
        self.toggled_image = ""
        default_image_size = 0
        self.num_toggled = 0
        i = 0
        for image_name in image_names:
            image_size = HobPage._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size)

            image_attr = ("run" if (self.test_type_runnable(image_name) and self.test_mach_runnable(image_name)) else \
                          ("deploy" if self.test_deployable(image_name) else ""))
            is_toggled = (image_attr != "")

            if not self.toggled_image:
                if i == (len(image_names) - 1):
                    is_toggled = True
                if is_toggled:
                    default_image_size = image_size
                    self.toggled_image = image_name

            split_stuff = image_name.split('.')
            if "rootfs" in split_stuff:
                image_type = image_name[(len(split_stuff[0]) + len(".rootfs") + 1):]
            else:
                image_type = image_name[(len(split_stuff[0]) + 1):]

            self.image_store.append({'name': image_name,
                                    'type': image_type,
                                    'size': image_size,
                                    'is_toggled': is_toggled,
                                    'action_attr': image_attr,})

            i = i + 1
            self.num_toggled += is_toggled

        is_runnable = self.create_bottom_buttons(self.buttonlist, self.toggled_image)

        # Generated image files info
        varlist = ["Name: ", "Files created: ", "Directory: "]
        vallist = []

        vallist.append(image_name.split('.')[0])
        vallist.append(',  '.join(fileitem['type'] for fileitem in self.image_store))
        vallist.append(image_addr)

        view_files_button = HobAltButton("View files")
        view_files_button.connect("clicked", self.view_files_clicked_cb, image_addr)
        view_files_button.set_tooltip_text("Open the directory containing the image files")
        open_log_button = None
        if log_file:
            open_log_button = HobAltButton("Open log")
            open_log_button.connect("clicked", self.open_log_clicked_cb, log_file)
            open_log_button.set_tooltip_text("Open the build's log file")
        self.image_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=view_files_button, button2=open_log_button)
        self.box_group_area.pack_start(self.image_detail, expand=False, fill=True)

        # The default kernel box for the qemu images
        self.sel_kernel = ""
        self.kernel_detail = None
        if 'qemu' in image_name:
            self.sel_kernel = self.get_kernel_file_name()

        #    varlist = ["Kernel: "]
        #    vallist = []
        #    vallist.append(self.sel_kernel)

        #    change_kernel_button = HobAltButton("Change")
        #    change_kernel_button.connect("clicked", self.change_kernel_cb)
        #    change_kernel_button.set_tooltip_text("Change qemu kernel file")
        #    self.kernel_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=change_kernel_button)
        #    self.box_group_area.pack_start(self.kernel_detail, expand=True, fill=True)

        # Machine, Base image and Layers
        layer_num_limit = 15
        varlist = ["Machine: ", "Base image: ", "Layers: "]
        vallist = []
        self.setting_detail = None
        if self.build_succeeded:
            vallist.append(machine)
            vallist.append(base_image)
            i = 0
            for layer in layers:
                varlist.append(" - ")
                if i > layer_num_limit:
                    break
                i += 1
            vallist.append("")
            i = 0
            for layer in layers:
                if i > layer_num_limit:
                    break
                elif i == layer_num_limit:
                    vallist.append("and more...")
                else:
                    vallist.append(layer)
                i += 1

            edit_config_button = HobAltButton("Edit configuration")
            edit_config_button.set_tooltip_text("Edit machine, base image and recipes")
            edit_config_button.connect("clicked", self.edit_config_button_clicked_cb)
            self.setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_config_button)
            self.box_group_area.pack_start(self.setting_detail, expand=True, fill=True)

        # Packages included, and Total image size
        varlist = ["Packages included: ", "Total image size: "]
        vallist = []
        vallist.append(pkg_num)
        vallist.append(default_image_size)
        if self.build_succeeded:
            edit_packages_button = HobAltButton("Edit packages")
            edit_packages_button.set_tooltip_text("Edit the packages included in your image")
            edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb)
        else: # get to this page from "My images"
            edit_packages_button = None
        self.package_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_packages_button)
        self.box_group_area.pack_start(self.package_detail, expand=True, fill=True)

        # pack the buttons at the bottom, at this time they are already created.
        if self.build_succeeded:
            self.box_group_area.pack_end(self.details_bottom_buttons, expand=False, fill=False)
        else: # for "My images" page
            self.details_separator = gtk.HSeparator()
            self.box_group_area.pack_start(self.details_separator, expand=False, fill=False)
            self.box_group_area.pack_start(self.details_bottom_buttons, expand=False, fill=False)

        self.show_all()
        if self.kernel_detail and (not is_runnable):
            self.kernel_detail.hide()

    def view_files_clicked_cb(self, button, image_addr):
        subprocess.call("xdg-open /%s" % image_addr, shell=True)

    def open_log_clicked_cb(self, button, log_file):
        if log_file:
            log_file = "file:///" + log_file
            gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)

    def refresh_package_detail_box(self, image_size):
        self.package_detail.update_line_widgets("Total image size: ", image_size)

    def test_type_runnable(self, image_name):
        type_runnable = False
        for t in self.builder.parameters.runnable_image_types:
            if image_name.endswith(t):
                type_runnable = True
                break
        return type_runnable

    def test_mach_runnable(self, image_name):
        mach_runnable = False
        for t in self.builder.parameters.runnable_machine_patterns:
            if t in image_name:
                mach_runnable = True
                break
        return mach_runnable

    def test_deployable(self, image_name):
        if self.builder.configuration.curr_mach.startswith("qemu"):
            return False
        deployable = False
        for t in self.builder.parameters.deployable_image_types:
            if image_name.endswith(t):
                deployable = True
                break
        return deployable

    def get_kernel_file_name(self, kernel_addr=""):
        kernel_name = ""

        if not kernel_addr:
            kernel_addr = self.builder.parameters.image_addr

        files = [f for f in os.listdir(kernel_addr) if f[0] <> '.']
        for check_file in files:
            if check_file.endswith(".bin"):
                name_splits = check_file.split(".")[0]
                if self.builder.parameters.kernel_image_type in name_splits.split("-"):
                    kernel_name = check_file
                    break

        return kernel_name

    def show_builded_images_dialog(self, widget, primary_action=""):
        title = primary_action if primary_action else "Your builded images"
        dialog = CrumbsDialog(title, self.builder,
                              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
        dialog.set_border_width(12)

        label = gtk.Label()
        label.set_use_markup(True)
        label.set_alignment(0.0, 0.5)
        label.set_padding(12,0)
        if primary_action == "Run image":
            label.set_markup("<span font_desc='12'>Select the image file you want to run:</span>")
        elif primary_action == "Deploy image":
            label.set_markup("<span font_desc='12'>Select the image file you want to deploy:</span>")
        else:
            label.set_markup("<span font_desc='12'>Select the image file you want to %s</span>" % primary_action)
        dialog.vbox.pack_start(label, expand=False, fill=False)

        # filter created images as action attribution (deploy or run)
        action_attr = ""
        action_images = []
        for fileitem in self.image_store:
            action_attr = fileitem['action_attr']
            if  (action_attr == 'run' and primary_action == "Run image") \
             or (action_attr == 'deploy' and primary_action == "Deploy image"):
                action_images.append(fileitem)

        # pack the corresponding 'runnable' or 'deploy' radio_buttons, if there has no more than one file.
        # assume that there does not both have 'deploy' and 'runnable' files in the same building result
        # in possible as design.
        curr_row = 0
        rows = (len(action_images)) if len(action_images) < 10 else 10
        table = gtk.Table(rows, 10, True)
        table.set_row_spacings(6)
        table.set_col_spacing(0, 12)
        table.set_col_spacing(5, 12)

        sel_parent_btn = None
        for fileitem in action_images:
            sel_btn = gtk.RadioButton(sel_parent_btn, fileitem['type'])
            sel_parent_btn = sel_btn if not sel_parent_btn else sel_parent_btn
            sel_btn.set_active(fileitem['is_toggled'])
            sel_btn.connect('toggled', self.table_selected_cb, fileitem)
            if curr_row < 10:
                table.attach(sel_btn, 0, 4, curr_row, curr_row + 1, xpadding=24)
            else:
                table.attach(sel_btn, 5, 9, curr_row - 10, curr_row - 9, xpadding=24)
            curr_row += 1

        dialog.vbox.pack_start(table, expand=False, fill=False, padding=6)

        button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
        HobAltButton.style_button(button)

        if primary_action:
            button = dialog.add_button(primary_action, gtk.RESPONSE_YES)
            HobButton.style_button(button)

        dialog.show_all()

        response = dialog.run()
        dialog.destroy()

        if response != gtk.RESPONSE_YES:
            return

        for fileitem in self.image_store:
            if fileitem['is_toggled']:
                if fileitem['action_attr'] == 'run':
                    self.builder.runqemu_image(fileitem['name'], self.sel_kernel)
                elif fileitem['action_attr'] == 'deploy':
                    self.builder.deploy_image(fileitem['name'])

    def table_selected_cb(self, tbutton, image):
        image['is_toggled'] = tbutton.get_active()
        if image['is_toggled']:
            self.toggled_image = image['name']

    def change_kernel_cb(self, widget):
        kernel_path = self.builder.show_load_kernel_dialog()
        if kernel_path and self.kernel_detail:
            import os.path
            self.sel_kernel = os.path.basename(kernel_path)
            markup = self.kernel_detail.format_line("Kernel: ", self.sel_kernel)
            label = ((self.kernel_detail.get_children()[0]).get_children()[0]).get_children()[0]
            label.set_markup(markup)

    def create_bottom_buttons(self, buttonlist, image_name):
        # Create the buttons at the bottom
        created = False
        packed = False
        self.button_ids = {}
        is_runnable = False

        # create button "Deploy image"
        name = "Deploy image"
        if name in buttonlist and self.test_deployable(image_name):
            deploy_button = HobButton('Deploy image')
            #deploy_button.set_size_request(205, 49)
            deploy_button.set_tooltip_text("Burn a live image to a USB drive or flash memory")
            deploy_button.set_flags(gtk.CAN_DEFAULT)
            button_id = deploy_button.connect("clicked", self.deploy_button_clicked_cb)
            self.button_ids[button_id] = deploy_button
            self.details_bottom_buttons.pack_end(deploy_button, expand=False, fill=False)
            created = True
            packed = True

        name = "Run image"
        if name in buttonlist and self.test_type_runnable(image_name) and self.test_mach_runnable(image_name):
            if created == True:
                # separator
                #label = gtk.Label(" or ")
                #self.details_bottom_buttons.pack_end(label, expand=False, fill=False)

                # create button "Run image"
                run_button = HobAltButton("Run image")
            else:
                # create button "Run image" as the primary button
                run_button = HobButton("Run image")
                #run_button.set_size_request(205, 49)
                run_button.set_flags(gtk.CAN_DEFAULT)
                packed = True
            run_button.set_tooltip_text("Start up an image with qemu emulator")
            button_id = run_button.connect("clicked", self.run_button_clicked_cb)
            self.button_ids[button_id] = run_button
            self.details_bottom_buttons.pack_end(run_button, expand=False, fill=False)
            created = True
            is_runnable = True

        name = "Build new image"
        if name in buttonlist:
            # create button "Build new image"
            if packed:
                build_new_button = HobAltButton("Build new image")
            else:
                build_new_button = HobButton("Build new image")
                build_new_button.set_flags(gtk.CAN_DEFAULT)
            #build_new_button.set_size_request(205, 49)
            self.details_bottom_buttons.pack_end(build_new_button, expand=False, fill=False)
            build_new_button.set_tooltip_text("Create a new image from scratch")
            button_id = build_new_button.connect("clicked", self.build_new_button_clicked_cb)
            self.button_ids[button_id] = build_new_button

        return is_runnable

    def deploy_button_clicked_cb(self, button):
        if self.toggled_image:
            if self.num_toggled > 1:
                self.set_sensitive(False)
                self.show_builded_images_dialog(None, "Deploy image")
                self.set_sensitive(True)
            else:
                self.builder.deploy_image(self.toggled_image)

    def run_button_clicked_cb(self, button):
        if self.toggled_image:
            if self.num_toggled > 1:
                self.set_sensitive(False)
                self.show_builded_images_dialog(None, "Run image")
                self.set_sensitive(True)
            else:
                self.builder.runqemu_image(self.toggled_image, self.sel_kernel)

    def build_new_button_clicked_cb(self, button):
        self.builder.initiate_new_build_async()

    def edit_config_button_clicked_cb(self, button):
        self.builder.show_configuration()

    def edit_packages_button_clicked_cb(self, button):
        self.builder.show_packages(ask=False)

    def my_images_button_clicked_cb(self, button):
        self.builder.show_load_my_images_dialog()

    def settings_button_clicked_cb(self, button):
        # Create an advanced settings dialog
        response, settings_changed = self.builder.show_simple_settings_dialog()
        if not response:
            return
        if settings_changed:
            self.builder.reparse_post_adv_settings()
