# loemu - A user friendly frontend for game emulators
# 
# Copyright (C) 2008 Bernat Pegueroles Forcadell
# 
# Author: Bernat Pegueroles Forcadell <bernat@pegueroles.cat>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import os
import sys

import gtk

from SimpleGladeApp import SimpleGladeApp

from Config import config
from Gamelist import Builder
from gobject import TYPE_STRING,TYPE_BOOLEAN

from time import sleep
from tempfile import mkstemp
from subprocess import Popen,PIPE
from signal import SIGTERM
from os.path import basename,dirname,join

try:
    from thread import start_new_thread
except ImportError:
    from dummy_thread import start_new_thread

class PreferencesDialog(SimpleGladeApp):

    def __init__(self, path="Preferences.glade",
                 root="preferences_dialog",
                 domain=config.app_name, **kwargs):
        path = os.path.join(config.get_dir('glade'), path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    def new(self):     
        self.gamelist_rebuilded=True

        self.gamelist=config.get("General","gamelist")
        alignment=self.gamelist_button.get_children()[0]
        alignment.set(0,0,0,0)
        bbox = alignment.get_children()[0]
        image, label = bbox.get_children()
        label.set_text(" "+basename(self.gamelist))

        images={
            'good': 
                gtk.image_new_from_file(
                    join(config.get_dir('images'),'state_1.gif')),
            'best available': 
                gtk.image_new_from_file(
                    join(config.get_dir('images'),'state_2.gif')),
            'bad': 
                gtk.image_new_from_file(
                    join(config.get_dir('images'),'state_3.gif')),
            'unknown': 
                gtk.image_new_from_file(
                    join(config.get_dir('images'),'state_4.gif'))}

        match=config.get("General",'states')
        for widget in self.get_widget('states').get_children():
            label=widget.get_label()
            key=config.get_key(label)
            widget.set_image(images[key])
            if key in match:
                widget.set_active(True)

        self.set_combobox(  "gamelist_format",
                            ['xml','python'],
                            config.get("General",'gamelist_format'))
        self.set_emulators()
        self.set_combobox(  "emulator",
                            config.emulators(),
                            config.emulators()[0])

        self.set_checkbuttons("states")
        self.set_treeview("filters",['category','year','manufacturer','nplayers','version'])
        self.set_treeview("finders",config.fields.copy())
        self.set_treeview("columns",config.fields.copy())
        self.set_treeview("show",config.fields.copy())

        self.image_filename=config.get("General","default_image")
        if os.access(self.image_filename,os.R_OK):
            try:
                pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(self.image_filename,120,120)
                self.default_image.set_from_pixbuf(pixbuf)
            except:
                pass
        return

    def set_combobox(self,key,list,default):
        widget=self.get_widget(key)

        liststore = gtk.ListStore(TYPE_STRING)
        for item in list:
            liststore.append([item])
        widget.set_model(liststore)
        cell = gtk.CellRendererText()
        widget.pack_start(cell, True)
        widget.add_attribute(cell, 'text', 0)
        match=config.get_key(default)
        widget.set_active(list.index(match))
        return

    def set_checkbuttons(self,key):
        match=config.get("General",key)
        for widget in self.get_widget(key).get_children():
            label=widget.get_label()
            if config.get_key(label) in match:
                widget.set_active(True)
        return

    def set_treeview(self,key,fields):
        column=list()
        treeview=self.get_widget(key)

        cell = gtk.CellRendererToggle()
        cell.set_property("activatable",True)
        cell.connect("toggled", self.change_row_value,treeview)
        column= gtk.TreeViewColumn()
        column.pack_start(cell, True)
        treeview.append_column(column)
        column.add_attribute(cell,'active',0)
        column.set_reorderable(False)
        column.set_resizable(False)
        column.set_min_width(30)

        cell = gtk.CellRendererText()
        column= gtk.TreeViewColumn()
        treeview.append_column(column)
        column.pack_start(cell, True)
        column.set_attributes(cell, text=1)
        column.set_reorderable(False)
        column.set_resizable(False)

        treeview.set_reorderable(True)
        treeview.set_enable_search(True)

        args=[TYPE_BOOLEAN,TYPE_STRING]
        liststore = gtk.ListStore(*args)

        selection=treeview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        
        for name in filter(lambda x: x not in ['state','favorite'],config.get("General",key)):
            line = list()
            label=config.get_title(name)
            line.append(True)
            line.append(label)
            liststore.append(line)
            if name in fields:
                fields.remove(name)
        for name in filter(lambda x: x not in ['state','favorite'],fields):
            line = list()
            label=config.get_title(name)
            line.append(False)
            line.append(label)
            liststore.append(line)
        
        treeview.columns_autosize()
        treeview.set_model(liststore)
        return

    def change_row_value(self, widget, path, treeview, *args):
        model= treeview.get_model()
        model[path][0] = not model[path][0]
        return

    def change_emulator(self, widget, *args):
        id=self.emulator.get_active_text()
        self.params_viewport.remove(self.params_viewport.get_child())
        self.params_viewport.add(self.emulators[id])
        while gtk.events_pending():
            gtk.main_iteration_do(False)
        self.params_viewport.show_all()
        return

    def set_emulators(self,*args):
        self.emulators=dict()
        self.params=dict()
        for id in config.emulators():
            vbox=gtk.VBox()
            self.emulators[id]=vbox
            table=gtk.Table(1,2,False)
            table.set_border_width(5)
            table.set_row_spacings(10)
            table.set_col_spacings(10)
            vbox.pack_start(table,False)

            emu=config.emu(id)
            rows=0
            self.params[id]=dict()
            for key in emu.get("General","variables"):
                rows+=1
                if emu.has_option(key,"text"):
                    label=gtk.Label(_(emu.get(key,"text")))
                else:
                    label=gtk.Label("param "+key)
                label.set_alignment(0,0.5)
                label.set_line_wrap(True)
                label.set_size_request(250,-1)
                
                table.resize(rows,2)
                table.attach(label,0,1,rows-1,rows)
                
                if emu.has_option(key,"type"):
                    type=emu.get(key,"type")
                else:
                    type="text"
                
                if type == "select_file":
                    widget=gtk.FileChooserButton(_("Select file"))
                    widget.set_action(gtk.FILE_CHOOSER_ACTION_OPEN)
                    value=emu.get("Variables",key)
                    if len(value) != 0 and not value.startswith('/'):
                        for path in os.environ['PATH'].split(':'):
                            if os.access(path+'/'+value,os.R_OK):
                                value= path+'/'+value
                                break
                    if os.access(value,os.R_OK):
                        widget.set_filename(value)
                elif type == "select_directory":
                    widget=gtk.FileChooserButton(_("Select directory"))
                    widget.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
                    value=emu.get("Variables",key)
                    if os.access(value,os.R_OK):
                        widget.set_filename(value)
                elif type == "images":
                    widget = gtk.Button(stock=gtk.STOCK_SELECT_COLOR)
                    widget.connect("clicked", self.image_sets_dialog,key)
                    alignment = widget.get_children()[0]
                    alignment.set(0,0,0,0)
                    bbox = alignment.get_children()[0]
                    image, label = bbox.get_children()
                    label.set_text(" "+_("Image Sets"))
                else:
                    widget=gtk.Entry()
                    widget.set_text(str(emu.get("Variables",key)))
                
                box=gtk.VBox()
                box.pack_start(widget,False)
                table.attach(box,1,2,rows-1,rows)
                self.params[id][key]=widget
        return

    def image_sets_dialog(self,widget,param):
        id=self.emulator.get_active_text()
        emu=config.emu(id)
        
        dialog = ImagesDialog()
        dialog.images_dialog.set_transient_for(self.preferences_dialog)
        dialog.set_list(emu.get("Variables",param))
        
        response= dialog.images_dialog.run()
        if response == gtk.RESPONSE_OK:
            emu.set("Variables",param,repr(dialog.get_list()))
        dialog.images_dialog.hide()
        return

    def update_configuration(self):
        config.set("General","default_image",self.image_filename)
        config.set("General","gamelist",self.gamelist)
        config.set("General","gamelist_format",self.gamelist_format.get_active_text())

        self.keep_checkbutton_values("states")
        self.keep_treeview_values("filters")
        self.keep_treeview_values("finders")
        self.keep_treeview_values("columns")
        self.keep_treeview_values("show")

        map(self.keep_emulator_values,config.emulators())
        config.write_configuration()
        return

    def keep_treeview_values(self,key):
        values=list()
        model= self.get_widget(key).get_model()
        iter= model.get_iter_first()
        while iter:
            if model.get_value(iter,0):
                values.append(config.get_key(model.get_value(iter,1)))
            iter= model.iter_next(iter)
        config.set("General",key,repr(values))
        return
        
    def keep_checkbutton_values(self,key):
        values=list()
        for widget in self.glade.get_widget(key).get_children():
            if widget.get_active():
                values.append(config.get_key(widget.get_label()))
        config.set("General",key,repr(values))
        return
        
    def keep_emulator_values(self,emulator):
        emu=config.emu(emulator)
        for key,widget in self.params[emulator].iteritems():
            if emu.has_option(key,"type"):
                type=emu.get(key,"type")
            else:
                type="text"
            if type == "select_file":
                filename = widget.get_filename()
                if filename == None:
                    filename = ""
                emu.set("Variables",key,filename)
            elif type == "select_directory":
                filename = widget.get_filename()
                if filename == None:
                    filename = ""
                emu.set("Variables",key,filename)
            elif type == "images":
                pass
            else:
                emu.set("Variables",key,widget.get_text())
        return

    def open(self):
        self.gamelist_rebuilded = False
        self.preferences_dialog.show()
        return 
    
    def close(self):
        self.preferences_dialog.hide()
        return

    def gamelist_is_rebuilded(self):
        return self.gamelist_rebuilded

    def open_about_dialog(self, widget, *args):
        dialog = AboutDialog()
        dialog.about_dialog.set_transient_for(self.preferences_dialog)
        dialog.about_dialog.show()
        dialog.about_dialog.run()
        dialog.about_dialog.hide()
        return

    def set_gamelist_location(self, widget, *args):
        chooser = gtk.FileChooserDialog(_("Gamelist Location"),
            action=gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        if os.access(self.gamelist, os.R_OK):
            chooser.select_filename(self.gamelist)
        if chooser.run() != gtk.RESPONSE_OK:
            chooser.destroy()
            return
        filename= chooser.get_filename()
        chooser.destroy()
        if not filename:
            return
        self.gamelist=filename
        alignment=self.gamelist_button.get_children()[0]
        alignment.set(0,0,0,0)
        bbox = alignment.get_children()[0]
        image, label = bbox.get_children()
        label.set_text(" "+basename(self.gamelist))
        return

    def set_default_image(self, widget, *args):
        chooser = gtk.FileChooserDialog(_("Default Image"),
            action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        chooser.select_filename(self.image_filename)
        self.preview_image=gtk.Image()
        box=gtk.HBox()
        box.set_size_request(150,150)
        box.pack_start(self.preview_image)
        chooser.set_extra_widget(box)
        chooser.connect("update-preview", self.update_preview_image)

        try:
            pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(self.image_filename,120,120)
            self.preview_image.set_from_pixbuf(pixbuf)
            self.preview_image.show_all()
        except:
            pass

        if chooser.run() != gtk.RESPONSE_OK:
            chooser.destroy()
            return

        self.image_filename=chooser.get_filename()
        if self.image_filename == None:
            self.image_filename=""
            chooser.destroy()
            return
        try:
            pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(self.image_filename,120,120)
        except:
            self.image_filename=""
            chooser.destroy()
            return
        self.default_image.set_from_pixbuf(pixbuf)
        chooser.destroy()
        return

    def update_preview_image(self, widget, *args):
        filename = widget.get_filename()
        if filename == None:
            return
        try:
            pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(filename,120,120)
            self.preview_image.set_from_pixbuf(pixbuf)
        except:
            self.preview_image.set_from_stock(gtk.STOCK_MISSING_IMAGE,gtk.ICON_SIZE_DIALOG)
        self.preview_image.show_all()
        return

    def build_gamelist(self, widget, *args):
        if widget.get_name() == "build_complete":
            id = None
        else:
            id = self.emulator.get_active_text()
        self.update_configuration()
        dialog = BuildingDialog()
        dialog.building_dialog.set_transient_for(self.preferences_dialog)
        dlg=dialog.build(id)
        dlg.set_transient_for(self.preferences_dialog)
        dlg.run()
        dlg.destroy()
        self.gamelist_rebuilded=True
        return

    def treeview_action(self, widget, *args):
        stock=widget.get_child().get_stock()[0]
        if stock not in ['gtk-go-up','gtk-go-down']:
            return

        # XXX get treeview
        treeview=widget.get_parent().get_parent().get_children()[0].get_child()

        (liststore,iter)=treeview.get_selection().get_selected()
        if iter==None:
            return
        
        if stock == "gtk-go-up":
            num=liststore.get_path(iter)[0]
            if num==0:
                return
            prev_iter=liststore.get_iter(num-1)
            liststore.swap(iter,prev_iter)
            return
        if stock == "gtk-go-down":
            next_iter=liststore.iter_next(iter)
            if next_iter==None:
                return
            liststore.swap(iter,next_iter)
            return
        return


class ImagesDialog(SimpleGladeApp):

    def __init__(self, path="Preferences.glade",
                 root="images_dialog",
                 domain=config.app_name, **kwargs):
        path = os.path.join(config.get_dir('glade'), path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    def set_list(self,list):
        liststore=gtk.ListStore(TYPE_STRING)
        self.images_treeview.set_model(liststore)
        
        column=gtk.TreeViewColumn()
        self.images_treeview.append_column(column)
        cell = gtk.CellRendererText()
        column.pack_start(cell, True)
        column.set_attributes(cell, text=0)

        self.images_treeview.set_enable_search(False)
        for row in list:
            liststore.append([row])
    
    def get_list(self):
        liststore=self.images_treeview.get_model()
        values=list()
        iter=liststore.get_iter_first()
        while iter:
            values.append(liststore.get_value(iter,0))
            iter=liststore.iter_next(iter)
        return values

    def treeview_images_action(self, button, *args):
        name=button.get_name()
        # XXX actions from stock
        #print button.get_child().get_stock()
        #print button.get_children()
        for action in ['file','directory','remove','up','down']:
            if action in name:
                break;
        else:
            return
        liststore=self.images_treeview.get_model()
        if action == "file":
            chooser = gtk.FileChooserDialog(_("Image Sets"),
                action=gtk.FILE_CHOOSER_ACTION_OPEN,
                buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
            if chooser.run() == gtk.RESPONSE_OK:
                liststore.append([chooser.get_filename()])
            chooser.destroy()
            return
        if action == "directory":
            chooser = gtk.FileChooserDialog(_("Image Sets"),
                action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
            if chooser.run() == gtk.RESPONSE_OK:
                liststore.append([chooser.get_current_folder()])
            chooser.destroy()
            return
        
        (model,iter)=self.images_treeview.get_selection().get_selected()
        if iter==None:
            return
        
        if action == "remove":
            liststore.remove(iter)
            return
        if action == "up":
            num=liststore.get_path(iter)[0]
            if num==0:
                return
            prev_iter=liststore.get_iter(num-1)
            liststore.swap(iter,prev_iter)
            return
        if action == "down":
            next_iter=liststore.iter_next(iter)
            if next_iter==None:
                return
            liststore.swap(iter,next_iter)
            return
        return

class BuildingDialog(SimpleGladeApp):

    def __init__(self, path="Preferences.glade",
                 root="building_dialog",
                 domain=config.app_name, **kwargs):
        path = os.path.join(config.get_dir('glade'), path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    def build(self,id=None):
        self.title.set_markup("<b>"+_("Reading configuration ...")+"</b>")
        self.subtitle.set_text(" ")
        self.msg.set_text(" ")
        self.building_dialog.show()
        while gtk.events_pending():
            gtk.main_iteration_do(False)

        self.msg_dialog=gtk.MessageDialog(  flags = gtk.DIALOG_MODAL,
                                            type = gtk.MESSAGE_INFO,
                                            buttons = gtk.BUTTONS_OK)
        self.msg_dialog.set_markup("<b>"+_("Process completed!")+"</b>")

        args=list()
        args.append("loemu-builder")
        if id:
            args.append(id)
        process_environ=os.environ.copy()
        # XXX platform independent? no
        process_environ['PATH']=os.curdir+":"+process_environ['PATH']

        p=Popen(args,stdout=PIPE,env=process_environ)
        self.pid=p.pid
        self.process_is_running=True
        start_new_thread(self.builder_log_handler,(p.stdout,))

        self.progressbar.set_pulse_step(0.05)
        self.fraction=-1.0
        while self.process_is_running:
            if self.fraction < 0:
                self.progressbar.pulse()
                interval=0.1
            else:
                self.progressbar.set_fraction(self.fraction)
                interval=0.1
            while gtk.events_pending():
                gtk.main_iteration_do(False)
            sleep(interval)
        self.building_dialog.hide()
        return self.msg_dialog

    def builder_log_handler(self,stdout,*args):
        line=stdout.readline()
        while line != "":
            if line[0:2]=="00":
                self.msg.set_markup("<i>"+line[3:-1]+"</i>")
            elif line[0:2]=="10":
                self.title.set_markup("<b>"+line[3:-1]+"</b>")
            elif line[0:2]=="11":
                self.subtitle.set_text(line[3:-1])
            elif line[0:2]=="20":
                self.fraction=float(line[3:-1])
            elif line[0:2]=="90":
                self.title.set_markup("<b>"+line[3:-1]+"</b>")
                self.cancel_button.set_sensitive(False)
            elif line[0:2]=="99":
                self.msg_dialog=gtk.MessageDialog(  flags = gtk.DIALOG_MODAL,
                                                    type = gtk.MESSAGE_ERROR,
                                                    buttons = gtk.BUTTONS_OK)
                self.msg_dialog.set_markup("<b>"+_("Building canceled!")+"</b>")
                self.msg_dialog.format_secondary_text(line[3:-1])
            line=stdout.readline()
        self.process_is_running = False
        return

    def cancel(self, widget, *args):
        self.msg_dialog=gtk.MessageDialog(  flags = gtk.DIALOG_MODAL,
                                            type = gtk.MESSAGE_ERROR,
                                            buttons = gtk.BUTTONS_OK)
        self.msg_dialog.set_markup("<b>"+_("Building canceled!")+"</b>")
        os.kill(self.pid,SIGTERM)
        return True

class AboutDialog(SimpleGladeApp):

    def __init__(self, path="Preferences.glade",
                 root="about_dialog",
                 domain=config.app_name, **kwargs):
        path = os.path.join(config.get_dir('glade'), path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    def new(self):
        self.about_dialog.set_modal(True)
        self.about_dialog.set_version(config.app_version)
