#!/usr/bin/env python
# Copyright (C) 2010, 2011 Linaro
#
# Author: James Tunnicliffe <james.tunnicliffe@linaro.org>
#
# This file is part of Linaro Image Tools.
#
# Linaro Image Tools 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 2
# of the License, or (at your option) any later version.
#
# Linaro Image Tools 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 Linaro Image Tools; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
# USA.

import wx
import wx.html
import wx.wizard
import wx.wizard as wiz
import sys
import re
import os
import linaro_image_tools.fetch_image as fetch_image
import string
import operator
import Queue
import time
import datetime
from linaro_image_tools.fetch_image import (QEMU, HARDWARE)
from linaro_image_tools.media_create.check_device import (
    _get_system_bus_and_udisks_iface,
    _get_dbus_property,
    )
import dbus

def add_button(bind_to,
               sizer,
               label,
               style,
               select_event,
               hover_event,
               unhover_event):

    """Create a radio button with event bindings."""
    if(style != None):
        radio_button = wx.RadioButton(bind_to, label=label, style=style)
    else:
        radio_button = wx.RadioButton(bind_to, label=label)

    sizer.Add(radio_button, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, 5)
    bind_to.Bind(wx.EVT_RADIOBUTTON, select_event, radio_button)
    wx.EVT_ENTER_WINDOW(radio_button, hover_event)
    wx.EVT_LEAVE_WINDOW(radio_button, unhover_event)

    return radio_button


class ReleaseOrSnapshotPage(wiz.PyWizardPage):
    """Ask the user if they want to use a release or a snapshot"""

    def __init__(self, parent, config, db, pages, width):
        wiz.PyWizardPage.__init__(self, parent)
        self.config = config
        self.settings = self.config.settings
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.next = None
        self.prev = None
        self.db = db
        self.wizard = parent
        self.pages = pages
        self.width = width
        self.settings['image'] = None
        self.os_selected = True
        self.width = width

        message = ("Would you like to use a Linaro release, or a more up to "
                   "date, but possibly unstable build?")
        header = wx.StaticText(self, -1, message)
        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide

        self.box1 = wx.BoxSizer(wx.VERTICAL)

        self.button_text = {'release':  "I would like to run stable, "
                                        "tested software.",
                            'snapshot': "I would like to run untested, but "
                                        "more up-to-date software."}

        self.rel_btn = add_button(self, self.box1, self.button_text['release'],
                                  wx.RB_GROUP, self.event_radio_button_select,
                                  None, None)

        # Save the setting for the default selected value
        self.settings['release_or_snapshot'] = "release"

        self.snap_btn = add_button(self, self.box1,
                                   self.button_text['snapshot'], None,
                                   self.event_radio_button_select, None, None)

        self.cp = wx.CollapsiblePane(self, label="Advanced Options",
                                     style=wx.CP_DEFAULT_STYLE |
                                           wx.CP_NO_TLW_RESIZE)
        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.event_on_pane_changed,
                  self.cp)
        self.make_pane_content(self.cp.GetPane())
        self.box2 = wx.BoxSizer(wx.VERTICAL)
        self.box2.Add(self.cp, 0)

        self.help_text_main = wx.StaticText(self, -1, "")

        self.sizer.Add(header)
        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.sizer.Add(self.help_text_main, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.cp.SetSizer(self.sizer)
        self.SetSizerAndFit(self.sizer)
        self.sizer.Fit(self)
        self.Move((50, 50))

    def make_pane_content(self, pane):
        self.adv_box = wx.BoxSizer(wx.VERTICAL)

        message = ("You have the option of selecting from several OS images "
                   "and builds.")
        header = wx.StaticText(pane, -1, message)
        header.Wrap(self.width - 10)  # -10 because boarder below is 5 px wide
        self.adv_box.Add(header)
        self.grid1 = wx.FlexGridSizer(0, 2, 0, 0)
        self.grid2 = wx.FlexGridSizer(0, 2, 0, 0)
        self.grid3 = wx.FlexGridSizer(0, 1, 0, 0)

        platforms = []
        for key, value in self.settings['choice']['platform'].items():
            platforms.append(key)

        style = wx.CB_DROPDOWN | wx.CB_READONLY
        self.settings['platform'] = None

        self.cb_release = wx.ComboBox(pane, style=style)
        self.Bind(wx.EVT_COMBOBOX, self.event_cb_release, self.cb_release)

        self.cb_build = wx.ComboBox(pane, style=style)
        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)

        self.cb_image = wx.ComboBox(pane, style=style)
        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_os, self.cb_image)

        self.help_text = wx.StaticText(pane, -1, "")

        alignment = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP
        align_text = alignment | wx.ALIGN_CENTER_VERTICAL
        self.grid1.Add(self.cb_image, 0, alignment, 5)
        self.grid1.Add(wx.StaticText(pane, -1, "The OS to run"),
                          0, align_text, 5)

        self.grid2.Add(self.cb_release, 0, alignment, 5)
        self.grid2.Add(wx.StaticText(pane, -1, "Available Linaro releases"),
                          0, align_text, 5)

        self.grid2.Add(self.cb_build, 0, alignment, 5)
        self.grid2.Add(wx.StaticText(pane, -1, "Available milestones"),
                          0, align_text, 5)

        self.adv_box.Add(self.grid1, 0, alignment, 0)
        self.adv_box.Add(self.grid2, 0, alignment, 0)
        self.adv_box.Add(self.grid3, 0, alignment, 0)
        self.update_visibility()
        self.adv_box.Add(self.help_text, 0, alignment, 5)

        pane.SetSizer(self.adv_box)
        self.adv_box.Fit(pane)

        # The following line is here because when the pane is shown and grid2
        # is not visible and an option is then selected to make it visible,
        # the pane does not (and seemingly can not) resize to accommodate
        # the contents of grid2. This results in some content staying
        # invisible until the pane is hidden and shown again. This blank bit
        # of text is just there to bulk up the size of the pane so work around
        # this. Padding is the size of grid2 / 2 (padding is applied all around
        # the StaticText).

        self.grid3.Add(wx.StaticText(pane, -1, ""), 0, align_text,
                       self.grid2.GetSize().height/2)
        self.adv_box.Fit(pane)

    def update_visibility(self):
        if self.settings['release_or_snapshot'] == "snapshot":
            self.adv_box.Hide(self.grid2, True)
        else:
            self.adv_box.Show(self.grid2, True)

    def update_next_active(self):
        if self.build_available and self.os_selected:
            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
        else:
            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()

    def update_build_box(self):
        """Depending on what hardware has been chosen, the OS list may be
        restricted. Filter out anything that is unavailable."""
        self.cb_build.Clear()

        if self.settings['release_or_snapshot'] == "snapshot":
            # This combo box is not used by snapshot builds
            self.cb_build.SetValue("Not applicable")
            self.build_available = True
            return

        builds = self.db.get_builds(self.settings['platform'])
        self.cb_build.SetValue("No build available")

        for build in builds:
            if(self.db.hardware_is_available_for_platform_build(
                                          self.settings['compatable_hwpacks'],
                                          self.settings['platform'],
                                          build)
                and self.db.build_is_available_for_platform_image(
                                "release_binaries",
                                self.settings['platform'],
                                self.settings['image'],
                                build)):

                self.cb_build.Append(build)
                self.cb_build.SetValue(build)
                self.settings['release_build'] = build

        available_hwpacks = (
            self.db.get_available_hwpacks_for_hardware_build_plaform(
                                          self.settings['compatable_hwpacks'],
                                          self.settings['platform'],
                                          self.settings['release_build']))

        if len(available_hwpacks):
            self.settings['hwpack'] = available_hwpacks[0]
            self.build_available = True
        else:
            self.build_available = False

        self.update_next_active()

    def update_release_and_build_boxes(self):
        """Depending on what hardware has been chosen, some builds may be
           unavailable..."""
        self.cb_release.Clear()

        if self.settings['release_or_snapshot'] == "snapshot":
            # This combo box is not used by snapshot builds
            self.cb_release.SetValue("Not applicable")
            return

        if not self.os_selected:
            self.cb_release.SetValue("-")
            self.update_build_box()
            self.update_next_active()
            return

        default_release = None
        for platform, value in self.settings['choice']['platform'].items():
            if(self.db.hardware_is_available_for_platform(
                                          self.settings['compatable_hwpacks'],
                                          platform)
               and len(self.db.execute_return_list(
                               'select * from release_binaries '
                               'where platform == ? and image == ?',
                                (platform, self.settings['image'])))):

                if platform in self.settings['UI']['translate']:
                    platform = self.settings['UI']['translate'][platform]

                self.cb_release.Append(platform, platform.upper())
                if not default_release or default_release < platform:
                    default_release = platform

        if default_release in self.settings['UI']['reverse-translate']:
            default = self.settings['UI']['reverse-translate'][default_release]
        else:
            default = default_release

        if not self.settings['platform']:
            # No platform has been chose, go with the default
            self.settings['platform'] = default
            self.cb_release.SetValue(default_release)
        else:
            # Don't change the value of platform if it is set.
            pf = self.settings['UI']['translate'][self.settings['platform']]
            self.cb_release.SetValue(pf)

        self.update_build_box()
        self.update_next_active()

    def get_human_os_name(self, item):
        """Given an OS name from the database, return a human name (either
        translated from the YAML settings, or just prettified) and if it is a
        LEB OS or not"""

        item = re.sub("linaro-", "", item)  # Remove any linaro- decoration

        if item in self.settings['UI']['descriptions']:
            human_name = self.settings['UI']['descriptions'][item]
        else:
            # Make human_name look nicer...
            human_name = string.capwords(item)

        leb_search = re.search("^LEB:\s*(.*)$", human_name)

        if leb_search:
            return leb_search.group(1), True

        return human_name, False

    def fill_os_list(self):
        """Filter the list of OS's from the config file based on the users
        preferences so all choices in the list are valid (i.e. their hardware
        is supported for the build they have chosen)."""

        # select unique image from snapshot_binaries/release_binaries to
        # generate list
        os_list = None
        if self.settings['release_or_snapshot'] == "release":
            os_list = self.db.get_os_list_from('release_binaries')
        else:
            os_list = self.db.get_os_list_from('snapshot_binaries')

        self.cb_image.Clear()

        printed_tag = None
        last_name = None
        current_image_setting_valid = False

        for state in ["LEB", "other"]:
            for item in os_list:
                if item == "old":
                    # Old is a directory that sometimes hangs around,
                    # but isn't one we want to display
                    continue

                # Save the original, untouched image name for use later.
                # We give it a more human name for display
                original = item
                item = re.sub("linaro-", "", item)

                os_hardware_combo_available = (
                            self.db.image_hardware_combo_available(
                                    self.settings['release_or_snapshot'],
                                    original,
                                    self.settings['compatable_hwpacks']))

                if os_hardware_combo_available:
                    human_name, is_LEB = self.get_human_os_name(item)

                    if item == self.settings['image']:
                        current_image_setting_valid = True

                    if state == "LEB" and is_LEB:

                        if printed_tag != state:
                            self.cb_image.Append(
                                            "- Linaro Supported Releases -")
                            printed_tag = state

                        self.cb_image.Append(human_name, original)

                        if self.settings['image'] == None:
                            self.settings['image'] = original
                            self.os_selected = True

                    elif state != "LEB" and not is_LEB:
                        if printed_tag != state:
                            self.cb_image.Append(
                                            "- Community Supported Releases -")
                            printed_tag = state

                        self.cb_image.Append(human_name, original)

                    last_name = original

        if(    self.settings['image'] != None
           and current_image_setting_valid == False):
            # If we have an image setting, but it doesn't match the OS list, we
            # have switched OS list. It may be that adding/removing "linaro-"
            # from the name will get a match.

            if re.search("linaro-", self.settings['image']):
                test_name = re.sub("linaro-", "", self.settings['image'])
            else:
                test_name = "linaro-" + self.settings['image']

            if test_name in os_list:
                # Success! We have translated the name and can retain the
                # "old setting"
                self.settings['image'] = test_name
                current_image_setting_valid = True
                self.os_selected = True

        if(   self.settings['image'] == None
           or current_image_setting_valid == False):
            # This should only get hit if there are no LEBs available
            self.settings['image'] = last_name
            self.os_selected = True

        assert self.settings['image']

        # Make sure the visible selected value matches the saved setting
        self.cb_image.SetValue(
                            self.get_human_os_name(self.settings['image'])[0])

    def force_rel_snap_if_hw_requires(self):
        """
        If a hardware pack is only available on a release or snapshot build
        then force self.settings['release_or_snapshot'] and grey out the
        other radio button. Also display a helpful message.
        """

        snapshot_ok = False
        release_ok = False

        for hwpack in self.settings['compatable_hwpacks']:
            if self.db.hardware_is_available_in_table("snapshot_hwpacks",
                                                      hwpack):
                snapshot_ok = True
            if self.db.hardware_is_available_in_table("release_hwpacks",
                                                         hwpack):
                release_ok = True

        assert release_ok or snapshot_ok, ("release or snapshot should have"
                                           "a hardware pack available")

        self.rel_btn.Enable()
        self.snap_btn.Enable()
        message = ""

        if not release_ok:
            self.settings['release_or_snapshot'] = "snapshot"
            self.rel_btn.Disable()
            self.rel_btn.SetValue(False)
            self.snap_btn.SetValue(True)
            message = ("The hardware you have chosen is only available for "
                       "snapshot builds, so release builds have been disabled")

        if not snapshot_ok:
            self.settings['release_or_snapshot'] = "release"
            self.snap_btn.Disable()
            self.snap_btn.SetValue(False)
            self.rel_btn.SetValue(True)
            message = ("The hardware you have chosen is only available for "
                       "release builds, so snapshot builds have been disabled")

        self.help_text_main.SetLabel(message)
        self.help_text_main.Wrap(self.width - 10)

    def GetNext(self):
        if self.settings['release_or_snapshot'] == "release":
            return self.pages['lmc_settings']
        else:
            return self.pages['select_snapshot']

    def GetPrev(self):
        return self.pages['hardware_details']

    #--- Event(s) ---
    def event_radio_button_select(self, event):
        # The radio button can be release or snapshot
        self.radio_selected = event.GetEventObject().GetLabel()

        if self.radio_selected == self.button_text['release']:
            self.settings['release_or_snapshot'] = "release"
        else:
            self.settings['release_or_snapshot'] = "snapshot"

        self.event_on_pane_changed(event)

    def event_on_pane_changed(self, event):
        self.fill_os_list()
        self.update_release_and_build_boxes()
        self.update_visibility()
        self.Layout()
        self.update_next_active()

    def event_cb_release(self, evt):
        str = evt.GetString().encode('ascii').lower()
        if str in self.settings['UI']['reverse-translate']:
            str = self.settings['UI']['reverse-translate'][str]
        self.settings['platform'] = str

        self.update_build_box()

    def event_combo_box_build(self, evt):
        self.settings['release_build'] = evt.GetString().encode('ascii')

    def event_combo_box_os(self, evt):
        self.settings['image'] = self.cb_image.GetClientData(
                                                            evt.GetSelection())

        if self.settings['image']:  # Is None for items that aren't an OS
            self.os_selected = True
            image = re.sub("linaro-", "", self.settings['image'])

            if image + "::long" in self.settings['UI']['descriptions']:
                self.help_text.SetLabel(self.settings['UI']
                                                     ['descriptions']
                                                     [image + "::long"])
            else:
                self.help_text.SetLabel("")

        else:  # Have selected help text
            self.os_selected = False
            self.help_text.SetLabel("Please select an operating system to run "
                                    "on your chosen hardware.")

        self.help_text.Wrap(self.width - 10)
        self.update_release_and_build_boxes()
        self.update_next_active()
    #--- END event(s) ---


class AboutMyHardwarePage(wiz.PyWizardPage):
    """Ask the user about their hardware. This only asks about the board, not
       any specific hardware packs because there can be multiple names for the
       same hardware pack or sometimes a hardware pack is only available in the
       releases or snapshots repository. We whittle down the choice as we go
       and the user can chose a hardare pack (if they don't like the default)
       under advanced options in the Linaro Media Create options
       page"""

    def __init__(self, parent, config, db):
        wiz.PyWizardPage.__init__(self, parent)
        self.settings = config.settings
        self.db = db
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.box1 = wx.BoxSizer(wx.VERTICAL)
        self.box2 = wx.BoxSizer(wx.VERTICAL)
        self.next = None

        message = """\
This Wizard will write an operating system of your choosing to either a disk
image or to an MMC card. First we need to know what hardware you have."""
        header = wx.StaticText(self, label=message)

        #--- Hardware Combo Box ---
        # Make sure that the displayed release is the one set in settings if
        # no selection is made
        if "panda" in self.settings['choice']['hardware'].keys():
            default_hardware = "panda"
        else:
            default_hardware = self.settings['choice']['hardware'].keys()[-1]

        self.settings['hardware'] = default_hardware
        self.settings['compatable_hwpacks'] = (
                self.settings['choice']['hwpack'][self.settings['hardware']])

        self.cb_hardware = wx.ComboBox(self,
                            value=self.settings['choice']
                                               ['hardware']
                                               [default_hardware],
                            style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.Bind(wx.EVT_COMBOBOX,
                  self.event_combo_box_hardware,
                  self.cb_hardware)

        file_dev_grid = wx.FlexGridSizer(0, 1, 0, 0)
        line_1_grid = wx.FlexGridSizer(0, 2, 0, 0)
        self.box2.Add(file_dev_grid, 0, wx.EXPAND)

        # self.settings['write_to_file_or_device'] should match the first
        # button below...
        self.button_text = {'hardware': "I have a",
                            'sim': "I want to run on a hardware simulation."}

        self.settings['hw_or_qemu'] = HARDWARE
        add_button(self,
                   line_1_grid,
                   self.button_text['hardware'],
                   wx.RB_GROUP,
                   self.event_radio_button_select,
                   None, None)

        line_1_grid.Add(self.cb_hardware)
        file_dev_grid.Add(line_1_grid)

        add_button(self,
                   file_dev_grid,
                   self.button_text['sim'],
                   None,
                   self.event_radio_button_select,
                   None, None)

        self.sizer.Add(header)
        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.SetSizerAndFit(self.sizer)
        self.sizer.Fit(self)
        self.Move((50, 50))

    def on_page_changing(self):
        self.update_hardware_box()

    def update_hardware_box(self):
        self.cb_hardware.Clear()

        sorted_hardware_names = sorted(self.settings['choice']['hardware']
                                                                  .iteritems(),
                                       key=operator.itemgetter(1))

        for device_name, human_readable_name in sorted_hardware_names:
            for hwpack in self.settings['choice']['hwpack'][device_name]:
                if(self.db.hardware_is_available_in_table("snapshot_hwpacks",
                                                          hwpack)
                   or self.db.hardware_is_available_in_table("release_hwpacks",
                                                             hwpack)):
                    self.cb_hardware.Append(human_readable_name, device_name)
                    break

    #--- Event(s) ---
    def event_combo_box_hardware(self, event):
        self.settings['hardware'] = (event
                                     .GetEventObject()
                                      .GetClientData(event.GetSelection())
                                       .encode('ascii'))

        self.settings['compatable_hwpacks'] = (
                self.settings['choice']['hwpack'][self.settings['hardware']])
    def event_radio_button_select(self, event):
        val = event.GetEventObject().GetLabel()
        if val == self.button_text['sim']:
            self.settings['hw_or_qemu'] = QEMU
            assert "beagle" in self.settings['choice']['hardware'].keys()
            self.settings['hardware'] = "beagle"
            self.settings['compatable_hwpacks'] = (
                self.settings['choice']['hwpack'][self.settings['hardware']])

        elif val == self.button_text['hardware']:
            self.settings['hw_or_qemu'] = HARDWARE
    #--- END event(s) ---

    def SetNext(self, next):
        self.next = next

    def GetNext(self):
        return self.next

class SelectSnapshot(wiz.WizardPageSimple):
    """Present the user with a calendar widget and a list of builds available
    on the selected date so they can chose a snapshot. Filter out days when
    their chosen hardware does not have an available build."""

    def __init__(self, parent, config, db, width):
        wiz.WizardPageSimple.__init__(self, parent)
        self.settings = config.settings
        self.db = db
        self.wizard = parent
        self.width = width
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        header = wx.StaticText(self,
                               label="Builds are created most days. First "
                                     "please select the day on which the "
                                     "build you would like to use was built,"
                                     " then, if there was more than one "
                                     "build that day you will be able to "
                                     "select the build number.")
        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide

        box1 = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(header)

        # Set today as the default build date in settings
        # (matches the date picker)
        self.today = wx.DateTime()
        self.today.SetToCurrent()
        self.settings['build_date'] = self.today.FormatISODate().encode('ascii')

        dpc = wx.DatePickerCtrl(self, size=(120, -1),
                                style=wx.DP_DEFAULT)
        self.Bind(wx.EVT_DATE_CHANGED, self.on_date_changed, dpc)

        #--- Build number Combo Box ---
        # Make sure that the displayed build is the one set in settings if no
        # selection is made
        self.settings['build_number'] = 0
        self.update_build()
        self.cb_build = wx.ComboBox(self,
                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_build, self.cb_build)

        #--- Layout ---
        # -- Combo boxes for hardware and image selection --

        grid2 = wx.FlexGridSizer(0, 2, 0, 0)
        grid2.Add(dpc, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        grid2.Add(self.cb_build, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        box1.Add(grid2, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        self.help_text = wx.StaticText(self)
        self.sizer.Add(self.help_text, 1, wx.EXPAND, 5)

        self.SetSizer(self.sizer)
        self.sizer.Fit(self)
        self.Move((50, 50))

    def update_platform(self):
        build_and_date = self.settings['snapshot_build'].split(":")

        if len(build_and_date) == 2:
            self.settings['platform'] = (
                    self.db.execute_return_list(
                            "select platform from snapshot_binaries "
                            "where date == ? and build == ?",
                            (build_and_date[0], build_and_date[1])))

            if len(self.settings['platform']) > 0:
                self.settings['platform'] = self.settings['platform'][0][0]

    def update_build(self):
        small_date = re.sub('-', '', self.settings['build_date'])
        self.settings['snapshot_build'] = (small_date
                                           + ":"
                                           + str(self.settings['build_number']))

    def fill_build_combo_box_for_date(self, date):
        """Every time a date is chosen, this function should be called. It will
        check to see if a compatible build is available. If there isn't, it
        will search for one and provide some help text to tell the user when
        compatable builds were built."""
        # Re-populate the build combo box

        self.cb_build.Clear()

        builds = self.db.get_binary_builds_on_day_from_db(
                                      self.settings['image'],
                                      date,
                                      self.settings['compatable_hwpacks'])

        if len(builds):
            max = 0
            for item in builds:
                #Always get a tuple, only interested in the first entry
                item = item[0]
                self.cb_build.Append(item, item.upper())

                if item > max:
                    max = item

            self.cb_build.SetValue(max)
            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
            self.help_text.SetLabel("")

        else:
            self.cb_build.SetValue("No builds available")
            future_date, past_date = self.db.get_next_prev_day_with_builds(
                                           self.settings['image'],
                                           date,
                                           self.settings['compatable_hwpacks'])

            help_text = None

            if future_date and past_date:
                help_text = ("There are no builds that match your "
                             "specifications available on the selected date. "
                             "The previous build was on " + past_date +
                             " and the next build was on " + future_date + ".")
            elif future_date:
                help_text = ("There are no builds that match your "
                             "specifications available on the selected date. "
                             "The next build was on " + future_date +
                             " and I couldn't find a past build (looked one "
                             "year back from the selected date).")
            elif past_date:
                help_text = ("There are no builds that match your "
                             "specifications available on the selected date. "
                             "The previous build was on " + past_date)
                if date != self.today.FormatISODate().encode('ascii'):
                    help_text += (" and I couldn't find a future build (I "
                                  "looked up to one year forward from the "
                                  "selected date).")
            else:
                help_text = ("I could not find any builds that match your "
                             "specifications close to the selected date (I "
                             "looked forward and back one year from the "
                             "selected date).")

            self.help_text.SetLabel(help_text)
            self.help_text.Wrap(self.width - 10)
            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()

    #--- Event(s) ---
    def on_date_changed(self, evt):
        self.settings['build_date'] = evt.GetDate().FormatISODate().encode('ascii')
        self.fill_build_combo_box_for_date(self.settings['build_date'])
        self.update_build()

    def event_combo_box_build(self, evt):
        self.settings['build_number'] = evt.GetString().encode('ascii').lower()
        self.update_build()
    #--- END event(s) ---


class LMC_settings(wiz.WizardPageSimple):
    """Present the user with, intially, the choice of writing the file system
    they are going to have created to a file, or directly to a device. Ask
    which file/device to write to.

    If writing to a device, the user is asked to tick a box saying that they
    understand that the device they have chosen will be erased.

    If the user ticks the advanced box, more options are shown."""

    def __init__(self, parent, config, db, width):
        wiz.WizardPageSimple.__init__(self, parent)
        self.settings = config.settings
        self.wizard = parent
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.yes_use_mmc = False
        self.db = db

        self.settings['path_selected'] = ""

        header = wx.StaticText(self,
                               label="Media Creation Settings\n\n"
                               "Please select if you would like to write the "
                               "file system I am about to create to a memory "
                               "card, or to a file on the local file system.")
        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide

        #--- Layout ---
        self.sizer.Add(header, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        box1 = wx.BoxSizer(wx.VERTICAL)
        file_dev_grid = wx.FlexGridSizer(0, 2, 0, 0)
        box1.Add(file_dev_grid, 0, wx.EXPAND)

        # self.settings['write_to_file_or_device'] should match the first
        # button below...
        self.settings['write_to_file_or_device'] = "file"
        self.file_button = add_button(
                   self,
                   file_dev_grid,
                   "Write to file",
                   wx.RB_GROUP,
                   self.event_radio_button_select,
                   None, None)

        self.device_button = add_button(
                   self,
                   file_dev_grid,
                   "Write to device",
                   None,
                   self.event_radio_button_select,
                   None, None)

        self.help_text_values = {"device": "Please select a device to write "
                                           "the file system to:",

                                 "file":   "Please select a file to write the "
                                           "file system to:"}

        self.help_text = wx.StaticText(
                             self,
                             label=self.help_text_values[
                                     self.settings['write_to_file_or_device']])
        self.help_text.Wrap(width - 10)

        #-- File/dev picker --
        file_browse_button = wx.Button(self, -1, "Browse")
        file_browse_grid   = wx.FlexGridSizer(0, 2, 0, 0)
        self.file_path_and_name = wx.TextCtrl(self, -1, "", size=(300, -1))

        file_browse_grid.Add(self.file_path_and_name, 0, wx.EXPAND)
        file_browse_grid.Add(file_browse_button, 0, wx.EXPAND)

        self.Bind(wx.EVT_BUTTON,
                  self.event_open_file_control,
                  file_browse_button)

        self.Bind(wx.EVT_TEXT,
                  self.event_file_path_and_name,
                  self.file_path_and_name)

        box1.Add(self.help_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        box1.Add(file_browse_grid, 0, wx.EXPAND)

        # Advanced settings collapsible pane
        self.cp = wx.CollapsiblePane(self, label="Advanced Options",
                                     style=wx.CP_DEFAULT_STYLE |
                                           wx.CP_NO_TLW_RESIZE)
        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.event_on_pane_changed,
                  self.cp)
        self.make_pane_content(self.cp.GetPane())
        self.box2 = wx.BoxSizer(wx.VERTICAL)
        self.box2.Add(self.cp)

        self.sizer.Add(box1, 0, wx.ALIGN_LEFT | wx.ALL, 0)
        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 0)
        self.SetSizer(self.sizer)
        self.sizer.Fit(self)
        self.Move((50, 50))

    def make_pane_content(self, pane):
        self.box = wx.BoxSizer(wx.VERTICAL)
        grid1 = wx.FlexGridSizer(0, 2, 0, 0)

        #--- Build some widgets ---
        #-- Target file system --
        file_systems = ["ext4", "btrfs", "ext3", "ext2"]
        default_target = file_systems[0]
        self.settings['rootfs'] = default_target
        cb_rootfs = wx.ComboBox(pane,
                                value=default_target,
                                style=wx.CB_DROPDOWN | wx.CB_READONLY)

        for item in file_systems:
            cb_rootfs.Append(item, item.upper())

        self.Bind(wx.EVT_COMBOBOX, self.event_combo_box_rootfs, cb_rootfs)

        #-- Image size spinner
        self.image_size_spinner = wx.SpinCtrl(pane, -1, "")
        self.Bind(wx.EVT_SPINCTRL,
                  self.event_image_size,
                  self.image_size_spinner)

        #-- Swap size spinner
        self.swap_size_spinner = wx.SpinCtrl(pane, -1, "")
        self.Bind(wx.EVT_SPINCTRL,
                  self.event_swap_size,
                  self.swap_size_spinner)


        alignment = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP
        grid1.Add(cb_rootfs, 0, alignment, 5)

        grid1.Add(wx.StaticText(pane,
                                label="The root file system of the image"),
                                0, alignment, 5)

        # We want to sub-devide the cell, to add another grid sizer...
        file_size_grid = wx.FlexGridSizer(0, 2, 0, 0)

        grid1.Add(file_size_grid,
                  0,
                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP)

        # Add a spinner that allows us to type/click a numerical value (defined above)
        file_size_grid.Add(self.image_size_spinner,
                           0,
                           alignment,
                           5)

        # Add a choice of MB or GB for size input
        units = ["GB", "MB"]
        self.size_unit = units[0]  # Set the default unit
        unit_choice = wx.Choice(pane, -1, (100, 50), choices=units)
        self.Bind(wx.EVT_CHOICE, self.event_chose_unit, unit_choice)
        file_size_grid.Add(unit_choice, 0, wx.ALIGN_RIGHT | wx.TOP, 5)

        # Back out of the extra grid, add some help text
        grid1.Add(wx.StaticText(
                            pane,
                            label="Writing to file only: Image file size"),
                            0,
                            alignment,
                            5)

        # The swap size (MB only)
        grid1.Add(self.swap_size_spinner,
                  0,
                  alignment,
                  5)

        grid1.Add(wx.StaticText(pane, label="Swap file size in MB"),
                  0,
                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                  5)

        self.cb_hwpacks = wx.ComboBox(
                                pane,
                                value=self.settings['compatable_hwpacks'][0],
                                style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.Bind(wx.EVT_COMBOBOX,
                  self.event_combo_box_hwpack,
                  self.cb_hwpacks)

        grid1.Add(self.cb_hwpacks,
                  0,
                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                  5)

        grid1.Add(wx.StaticText(pane, label="Compatible hardware packs"),
                  0,
                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                  5)
        desc_link = wx.HyperlinkCtrl(
                                pane, id=-1, url="",
                                label="Hardware pack descriptions")
        self.Bind(wx.EVT_HYPERLINK, self.event_hwpack_link, desc_link)
        grid1.Add(desc_link,
                  0,
                  wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                  5)

        self.box.Add(grid1, 0, alignment, 0)
        pane.SetSizer(self.box)
        self.box.Fit(pane)

    def on_activate(self):
        self.update_forward_active_and_mmc_confirm_box_visible()
        self.set_hwpacks_for_hardware()
        self.update_dev_file_buttons()

    def update_dev_file_buttons(self):
        if self.settings['hw_or_qemu'] == QEMU:
            self.device_button.Disable()
            self.device_button.SetValue(False)
            self.file_button.SetValue(True)
        else:
            self.device_button.Enable()

    def set_hwpacks_for_hardware(self):
        self.cb_hwpacks.Clear()

        if self.settings['release_or_snapshot'] == "snapshot":
            self.settings['build'] = self.settings['snapshot_build']

            date_and_build = self.settings['build'].split(":")

            compatable_hwpacks = (
                self.db.get_available_hwpacks_for_hardware_snapshot_build(
                                        self.settings['compatable_hwpacks'],
                                        self.settings['platform'],
                                        date_and_build[0],
                                        date_and_build[1]))
        else:
            self.settings['build'] = self.settings['release_build']
            compatable_hwpacks = (
                self.db.get_available_hwpacks_for_hardware_build_plaform(
                                        self.settings['compatable_hwpacks'],
                                        self.settings['platform'],
                                        self.settings['build']))

        for hwpack in compatable_hwpacks:
            self.cb_hwpacks.Append(hwpack)

        self.cb_hwpacks.SetStringSelection(compatable_hwpacks[0])
        self.settings['hwpack'] = compatable_hwpacks[0]

    def update_forward_active_and_mmc_confirm_box_visible(self):
        if(    self.settings['path_selected']
           and self.settings['path_selected'] != ""):
            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
        else:
            self.wizard.FindWindowById(wx.ID_FORWARD).Disable()

    # --- Event Handlers ---
    def event_on_pane_changed(self, event):
        self.Layout()

    def event_open_file_control(self, event):
        if self.settings['write_to_file_or_device'] == "file":

            dlg = wx.FileDialog(self,
                                message="Save file as ...",
                                defaultDir=os.getcwd(),
                                defaultFile="",
                                style=wx.SAVE)

        elif self.settings['write_to_file_or_device'] == "device":
            dlg = DevChoser(self, -1, "Please chose a device", self.settings)

        result = dlg.ShowModal()
        file_or_dev = self.settings['write_to_file_or_device']

        if result == wx.ID_OK or file_or_dev == "device":
            # The dev chooser doesn't do ok/cancel, it just finishes.
            if self.settings['write_to_file_or_device'] == "file":
                self.settings['path_selected'] = dlg.GetPaths()[0]
            self.file_path_and_name.SetValue(self.settings['path_selected'])

        dlg.Destroy()
        self.update_forward_active_and_mmc_confirm_box_visible()

    def event_file_path_and_name(self, event):
        self.settings['path_selected'] = event.GetString()
        self.update_forward_active_and_mmc_confirm_box_visible()

    def event_combo_box_hwpack(self, event):
        self.settings['hwpack'] = event.GetString().encode('ascii')

    def event_combo_box_rootfs(self, evt):
        self.settings['rootfs'] = evt.GetString().encode('ascii').lower()

    def event_hwpack_link(self, event):
        hw_desc = self.settings['UI']['hwpack-descriptions']
        body = "<h3>%s</h3><ul>" % self.settings['hardware']
        for hwpack in self.cb_hwpacks.GetItems():
            desc = ''
            if hw_desc.has_key(hwpack):
                desc = " - %s" % hw_desc[hwpack]
            body += "<li><b>%s</b><i>%s</i></li>" % (hwpack, desc)
        body += "</ul>"
        HtmlDialog( event.GetEventObject().GetParent(),
                    "Hardware Pack Descriptions: %s",
                    body)

    def event_radio_button_select(self, event):
        """Search the label of the button that has been selected to work out
        what we are writing to."""
        setting_search = re.search(
                            "write to (\w+)",
                            event
                             .GetEventObject()
                              .GetLabel()
                               .encode('ascii')
                                .lower())

        assert setting_search

        self.settings['write_to_file_or_device'] = setting_search.group(1)

        self.help_text.SetLabel(
               self.help_text_values[self.settings['write_to_file_or_device']])

        self.update_forward_active_and_mmc_confirm_box_visible()

    def event_pick_file_path(self, evt):
        self.settings['path_selected'] = os.path.abspath(evt.GetPath())
        self.update_forward_active_and_mmc_confirm_box_visible()

    def update_image_size_setting(self):
        if(self.image_size_spinner.GetValue() > 0):
            self.settings['image_size'] = (str(self.image_size_spinner
                                                                 .GetValue())
                                           + self.size_unit[0])
        else:
            self.settings['image_size'] = None

    def event_image_size(self, event):
        self.update_image_size_setting()

    def event_chose_unit(self, event):
        self.size_unit = event.GetString()
        self.update_image_size_setting()

    def event_swap_size(self, event):
        self.settings['swap_file'] = str(self.image_size_spinner.GetValue())

class HtmlDialog(wx.Dialog):
    def __init__(self, parent, title, content):
        wx.Dialog.__init__(self, parent, -1, title)
        html = wx.html.HtmlWindow(self)

        html.SetPage(content)
        self.SetSize(wx.Size(500,450))
        self.ShowModal()
        self.Destroy()

class DevChoser(wx.Dialog):
    def __init__(self, parent, id, title, settings):
        self.settings = settings
        wx.Dialog.__init__(self, parent, id, title)

        vbox = wx.BoxSizer(wx.VERTICAL)

        dev_info = self.get_device_info()
        dev_number = 1

        grid1 = wx.FlexGridSizer(0, len(dev_info[0].keys())+1, 0, 0)
        group = wx.RB_GROUP
        alignment = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP
        keys = dev_info[0].keys()

        grid1.Add(wx.StaticText(self, label=""))
        for key in keys:
            grid1.Add(wx.StaticText(self, label=key), 0, alignment, 5)

        self.id_dev_map = {}
        first = True
        for info in dev_info:
            button = add_button(self, grid1, str(dev_number), group,
                                self.event_radio_button_select, None, None)
            group = None

            for key in keys:
                message = info[key]
                grid1.Add(wx.StaticText(self, label=message), 0, alignment, 5)

                if key == "path":
                    self.id_dev_map[str(dev_number)] = info[key]

                    if self.settings['path_selected'] == info[key]:
                        button.SetValue(True)

                    if first and not self.settings['path_selected']:
                         self.settings['path_selected'] = info[key]

            dev_number += 1

        vbox.Add(grid1)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(self, -1, 'Use Selected Device')
        okButton.Bind(wx.EVT_BUTTON, self.OnCloseMe)
        hbox.Add(okButton, 1)

        vbox.Add(hbox, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)
        vbox.Fit(self)
        self.SetSizer(vbox)

    def event_radio_button_select(self, event):
        val = event.GetEventObject().GetLabel()
        self.settings['path_selected'] = self.id_dev_map[val]

    def get_device_info(self):
        """Get information about devices found on the system.
        """
        bus, udisks = _get_system_bus_and_udisks_iface()
        devices = udisks.get_dbus_method('EnumerateDevices')()
        devices.sort()
        dev_info = []
        for path in devices:
            device = bus.get_object("org.freedesktop.UDisks", path)
            props = ['drive-model', 'drive-vendor',
                     'drive-connection-interface', 'drive-media']
            info = {'path': _get_dbus_property('DeviceFile', device, path)}
            if(not _get_dbus_property('device-is-partition', device, path) and
               _get_dbus_property('device-is-media-available', device, path)):
                for prop in props:
                    info[prop] = str(_get_dbus_property(prop, device, path))
                dev_info.append(info)

        return dev_info

    def OnCloseMe(self, event):
        self.Close(True)

    def OnCloseWindow(self, event):
        self.Destroy()

class RunLMC(wiz.WizardPageSimple):
    """Present the user with some information about their choices and a button
    to start linaro-media-create. The linaro-media-create process is started in
    a new thread and important events are communicated back to the UI through a
    queue."""

    def __init__(self, parent, config, db, width):
        wiz.WizardPageSimple.__init__(self, parent)
        self.settings = config.settings
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.db = db
        self.width = width
        self.wizard = parent

        header = wx.StaticText(self, label="""Installing...""")
        header.Wrap(width - 10)  # -10 because boarder below is 5 pixels wide

        self.sizer.Add(header)
        self.box1 = wx.BoxSizer(wx.VERTICAL)

        # We expect to print 4 lines of information, reserve space using blank
        # lines.
        self.settings_summary_text = wx.StaticText(self, label="\n\n\n\n")
        self.settings_summary_text.Wrap(width - 10)

        self.box1.Add(self.settings_summary_text, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        self.start_button = wx.Button(self, 10, "Start", (20, 20))
        self.Bind(wx.EVT_BUTTON, self.start_lmc, self.start_button)

        self.start_button.SetToolTipString("Start creating an image, using the"
                                           "above settings.")

        self.start_button.SetSize(self.start_button.GetBestSize())
        self.box1.Add(self.start_button, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        self.download_guage = wx.Gauge(self,
                                       -1,
                                       1000,
                                       size=(self.width * 2 / 3, 25))

        self.status_grid = wx.FlexGridSizer(0, 2)

        self.status_grid.Add(wx.StaticText(self, label="Downloading files"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.downloading_files_status = wx.StaticText(self, label="")

        self.status_grid.Add(self.downloading_files_status,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.status_grid.Add(self.download_guage,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.downloading_files_info = wx.StaticText(self, label="")

        self.status_grid.Add(self.downloading_files_info,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.status_grid.Add(wx.StaticText(self, label="Unpacking downloads"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.unpacking_files_status = wx.StaticText(self, label="")

        self.status_grid.Add(self.unpacking_files_status,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.status_grid.Add(wx.StaticText(self, label="Installing packages"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.installing_packages_status = wx.StaticText(self, label="")

        self.status_grid.Add(self.installing_packages_status,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.status_grid.Add(wx.StaticText(self, label="Create file system"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.create_file_system_status = wx.StaticText(self, label="")

        self.status_grid.Add(self.create_file_system_status,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.status_grid.Add(wx.StaticText(self, label="Populate file system"),
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.populate_file_system_status = wx.StaticText(self, label="")

        self.status_grid.Add(self.populate_file_system_status,
                             0,
                             wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP,
                             5)

        self.box2 = wx.BoxSizer(wx.VERTICAL)
        self.blank = wx.StaticText(self, label="")  # Just like a spacer GIF...
        self.messages = wx.StaticText(self, label="")
        self.box2.Add(self.blank, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.box2.Add(self.messages, 0, wx.ALIGN_LEFT | wx.ALL, 5)

        self.sizer.Add(self.box1, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.sizer.Add(self.status_grid, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.sizer.Add(self.box2, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        self.SetSizerAndFit(self.sizer)
        self.sizer.Fit(self)
        self.Move((50, 50))

    def on_activate(self):
        """Called just before the page is displayed to update the text based on
        the users preferences."""

        # The build is stored in different forms depending on if we are using a
        # release or snapshot but from here on in it is a common value
        if self.settings['release_or_snapshot'] == "snapshot":
            self.settings['build'] = self.settings['snapshot_build']
        else:
            self.settings['build'] = self.settings['release_build']

        settings_summary = ("Press start to create an image with the "
                            "following settings:\n")
        settings_summary += "Operating System: " + self.settings['image'] + "\n"
        settings_summary += "Hardware: " + self.settings['hardware'] + "\n"

        # Assumption is that a file may be in a long path, we don't know how
        # big the font is and we don't want to allow the path to run off the
        # end of the line, so if a file is chosen, just show the file name.
        # Devices are (probably) /dev/some_short_name and the user really needs
        # to check them, so we show the whole thing.
        path = self.settings['path_selected']
        if self.settings['write_to_file_or_device'] == "file":
            path = self.settings['path_selected'].split(os.sep)[-1]

        settings_summary += (  "Writing image to "
                             + self.settings['write_to_file_or_device']
                             + " "
                             + path)

        self.settings_summary_text.SetLabel(settings_summary)
        self.settings_summary_text.Wrap(self.width - 10)

    def start_lmc(self, event):
        """Start a thread that runs linaro-media-create and a timer, which
        checks for UI updates every 100ms"""

        if self.settings['write_to_file_or_device'] == "file":
            self.settings['image_file'] = self.settings['path_selected']
        elif self.settings['write_to_file_or_device'] == "device":
            self.settings['mmc'] = self.settings['path_selected']

            title = 'Are you sure?'
            text = "Completely erase {0}?".format(self.settings['mmc'])
            dlg = wx.MessageDialog(None, text, title,
                                  wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
            if dlg.ShowModal() != wx.ID_YES:
                return
        else:
            assert False, ("self.config.settings['write_to_file_or_device'] "
                           "was an unexpected value"
                           + self.settings['write_to_file_or_device'])

        image_url, hwpack_url = self.db.get_image_and_hwpack_urls(self.settings)

        # Currently the UI is blocked when LMC is running, so grey out the
        # buttons to indicate to the user that they won't work!
        self.wizard.FindWindowById(wx.ID_BACKWARD).Disable()
        self.wizard.FindWindowById(wx.ID_CANCEL).Disable()

        if(image_url and hwpack_url):

            self.file_handler = fetch_image.FileHandler()

            self.timer = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.timer_ping, self.timer)
            self.timer.Start(milliseconds=100, oneShot=True)

            tools_dir = os.path.dirname(__file__)
            if tools_dir == '':
                tools_dir = None

            self.start_button.Disable()
            self.event_queue = Queue.Queue()
            self.lmc_thread = self.file_handler.LinaroMediaCreate(
                                                    image_url,
                                                    hwpack_url,
                                                    self.file_handler,
                                                    self.event_queue,
                                                    self.settings,
                                                    tools_dir)
            self.lmc_thread.start()
        else:
            print >> sys.stderr, ("Unable to find files that match the"
                                  "parameters specified")

    def timer_ping(self, event):
        """During start_lmc a timer is started to poll for events from
        linaro-media-create every 100ms. This is the function which is called
        to do that polling."""

        while not self.event_queue.empty():
            event = self.event_queue.get()

            if event[0] == "start":
                self.event_start(event[1])

            elif event[0] == "end":
                self.event_end(event[1])

            elif event == "terminate" or event == "abort":
                # Process complete. Enable next button.
                self.wizard.FindWindowById(wx.ID_FORWARD).Enable()
                if event == "terminate":
                    self.populate_file_system_status.SetLabel("Done")
                else:
                    self.populate_file_system_status.SetLabel("Failed")
                return  # Even if queue isn't empty, stop processing it

            elif event[0] == "update":
                self.event_update(event[1], event[2], event[3])

            elif event[0] == "message":
                self.messages.SetLabel(event[1])

            else:
                print >> sys.stderr, "timer_ping: Unhandled event", event

        self.timer.Start(milliseconds=50, oneShot=True)

    def unsigned_packages_query(self, package_list):
        message = ('In order to continue, I need to install some unsigned'
                   'packages into the image. Is this OK? The packages are:'
                   '\n\n' + package_list)

        dlg = wx.MessageDialog(self,
                               message,
                               'Install Unsigned Packages Into Image?',
                               wx.YES_NO | wx.NO_DEFAULT)

        choice = dlg.ShowModal()
        dlg.Destroy()

        return choice == wx.ID_YES

    #--- Event(s) ---
    def event_start(self, event):
        if event == "download":
            pass
        elif event == "unpack":
            self.unpacking_files_status.SetLabel("Running")
        elif event == "installing packages":
            self.installing_packages_status.SetLabel("Running")

        elif re.search('^unverified_packages:', event):
            # Get rid of event ID and whitespace invariance
            packages = " ".join(event.split()[1:])
            install_unsigned_packages = self.unsigned_packages_query(packages)

            if install_unsigned_packages == False:
                # TODO: Tidy up other threads
                sys.exit(1)
            else:
                self.lmc_thread.send_to_create_process("y")

        elif event == "create file system":
            self.create_file_system_status.SetLabel("Running")
        elif event == "populate file system":
            self.populate_file_system_status.SetLabel("Running")
        else:
            print "Unhandled start event:", event

    def event_end(self, event):
        if event == "download":
            self.downloading_files_status.SetLabel("Done")
        elif event == "unpack":
            self.unpacking_files_status.SetLabel("Done")
        elif event == "installing packages":
            self.installing_packages_status.SetLabel("Done")
        elif event == "create file system":
            self.create_file_system_status.SetLabel("Done")
        elif event == "populate file system":
            self.populate_file_system_status.SetLabel("Done")
        else:
            print "Unhhandled end event:", event

    def event_update(self, task, update_type, value):
        if task == "download":
            if update_type == "progress":
                self.total_bytes_downloaded += value

                time_difference = time.time() - self.old_time

                if time_difference > 1.0:
                    self.old_time = time.time()

                    # More than a second has passed since we calculated data
                    # rate
                    speed = (  float(  self.total_bytes_downloaded
                                     - self.old_bytes_downloaded)
                             / time_difference)

                    self.old_bytes_downloaded = self.total_bytes_downloaded

                    self.speeds.append(speed)

                    average_speed = 0
                    speeds_accumulated = 0
                    for speed in reversed(self.speeds):
                        average_speed += speed
                        speeds_accumulated += 1

                        if speeds_accumulated == 6:
                            break  # do rolling average of 6 seconds

                    average_speed /= speeds_accumulated

                    time_remaining = (  (  self.total_bytes_to_download
                                         - self.total_bytes_downloaded)
                                      / speed)

                    pretty_time = str(datetime.timedelta(seconds=int(
                                                              time_remaining)))

                    # Following table assumes we don't get past TBps internet
                    # connections soon :-)
                    units = ["Bps", "kBps", "MBps", "GBps", "TBps"]
                    units_index = 0
                    while speed > 1024:
                        speed /= 1024
                        units_index += 1

                    info = "Downloading at {0:.1f} {1}".format(
                                                         speed,
                                                         units[units_index])

                    self.downloading_files_status.SetLabel(info)

                    info = "{0} remaining".format(
                                                         pretty_time)

                    self.downloading_files_info.SetLabel(info)

                self.download_guage.SetValue(  1000
                                             * self.total_bytes_downloaded
                                             / self.total_bytes_to_download)

            elif update_type == "total bytes":
                self.old_time = time.time()
                self.old_bytes_downloaded = 0
                self.total_bytes_to_download = value
                self.total_bytes_downloaded = 0
                self.speeds = []  # keep an array of speeds used to calculate
                # the estimated time remaining - by not just using the
                # current speed we can stop the ETA bouncing around too much.

            elif update_type == "message":
                self.downloading_files_status.SetLabel(value)

    def event_combo_box_release(self, evt):
        pass

    def event_combo_box_build(self, evt):
        pass
    #--- END event(s) ---


class TestDriveWizard(wx.wizard.Wizard):
    def __init__(self, title):
        wx.wizard.Wizard.__init__(self, None, -1, title, wx.NullBitmap)
        self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)

    def on_page_changing(self, evt):
        """Executed before the page changes."""

        hw_details_pg = self.pages['hardware_details']
        rel_or_snap_pg = self.pages['release_or_snapshot']
        select_snap_pg = self.pages['select_snapshot']
        lmc_settings_pg = self.pages['lmc_settings']

        page = evt.GetPage()

        if evt.GetDirection():  # If going forwards...
            # Always enable back button if going forwards
            self.wizard.FindWindowById(wx.ID_BACKWARD).Enable()

            # If going from a select snapshot or select release page, record
            # which we were on so the back button of the next page works
            if self.config.settings['release_or_snapshot'] == "release":
                lmc_settings_pg.SetPrev(rel_or_snap_pg)
            else:
                select_snap_pg.SetNext(lmc_settings_pg)

                lmc_settings_pg.SetPrev(select_snap_pg)
                select_snap_pg.SetPrev(rel_or_snap_pg)

                if page == rel_or_snap_pg:
                    select_snap_pg.fill_build_combo_box_for_date(
                                            self.config.settings['build_date'])

            if page == hw_details_pg:
                rel_or_snap_pg.force_rel_snap_if_hw_requires()
                rel_or_snap_pg.fill_os_list()
                rel_or_snap_pg.update_release_and_build_boxes()

            if page == select_snap_pg:
                # Execute when exiting page
                select_snap_pg.update_platform()

            if(   page == select_snap_pg
               or (page == rel_or_snap_pg and
                   self.config.settings['release_or_snapshot'] == "release")):
                lmc_settings_pg.on_activate()

            if page == lmc_settings_pg:
                # Forward stays disabled until LMC has finished running
                self.wizard.FindWindowById(wx.ID_FORWARD).Disable()
                self.pages['run_lmc'].on_activate()

        else:  # Always enable the forward button if reversing into a page
            self.wizard.FindWindowById(wx.ID_FORWARD).Enable()

    def go(self):
        file_handler = fetch_image.FileHandler()
        self.config = fetch_image.FetchImageConfig()
        self.config.settings["force_download"] = False
        self.config.settings['compatable_hwpacks'] = ['foo']

        # If the settings file and server index need updating, grab them
        file_handler.update_files_from_server()

        # Load settings YAML, which defines the parameters we ask for and
        # acceptable responses from the user
        self.config.read_config(file_handler.settings_file)

        # Using the config we have, look up URLs to download data from in
        # the server index
        db = fetch_image.DB(file_handler.index_file)

        # Create the wizard and the pages
        self.wizard = wiz.Wizard(self, -1, "Linaro Media Builder")

        self.pages = {}
        self.pages['hardware_details']  = AboutMyHardwarePage(self.wizard,
                                                              self.config,
                                                              db)

        self.wizard.FitToPage(self.pages['hardware_details'])
        (width, height) = self.wizard.GetSize()

        self.pages['release_or_snapshot'] = ReleaseOrSnapshotPage(self.wizard,
                                                                  self.config,
                                                                  db,
                                                                  self.pages,
                                                                  width)

        self.pages['select_snapshot']   = SelectSnapshot(self.wizard,
                                                         self.config,
                                                         db,
                                                         width)

        self.pages['lmc_settings']      = LMC_settings(self.wizard,
                                                       self.config,
                                                       db,
                                                       width)

        self.pages['run_lmc']           = RunLMC(self.wizard,
                                                 self.config,
                                                 db,
                                                 width)

        self.pages['hardware_details'].SetNext(
                                            self.pages['release_or_snapshot'])

        self.pages['lmc_settings'].SetNext(self.pages['run_lmc'])
        self.pages['run_lmc'].SetPrev(self.pages['lmc_settings'])

        for (name, page) in self.pages.items():
            self.wizard.GetPageAreaSizer().Add(page)

        self.pages['hardware_details'].on_page_changing()
        self.wizard.RunWizard(self.pages['hardware_details'])


def run():
    """Wrapper around the full wizard. Is encapsulated in its own function to
       allow a restart to be performed, as described in __main___, easily"""
    app = wx.PySimpleApp()  # Start the application
    if app:
        pass  # We don't use this directly. Stop pyflakes complaining!

    w = TestDriveWizard('Simple Wizard')
    return w.go()

if __name__ == '__main__':
    run()
