# 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

import re
from Gamelist import Manager
from Config import config,Configuration
from Preferences import PreferencesDialog
from gobject import TYPE_INT,TYPE_STRING,TYPE_BOOLEAN
from zipfile import ZipFile,is_zipfile
from tempfile import mkstemp
from os.path import isdir,join
from time import sleep
import pango
try:
    from thread import start_new_thread
except ImportError:
    from dummy_thread import start_new_thread

class MainWindow(SimpleGladeApp):

    def __init__(self, path="Loemu.glade",
                 root="main_window",
                 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.main_window.maximize()

        self.gamelist=Manager()
        self.preferences_dialog=PreferencesDialog()

        self.define_games_treeview()

        self.define_filter_treeview()
        self.define_filter_combobox()
        
        self.image_sets=dict()
        self.define_show_game_zone()
        self.define_emu_combobox()
        self.finder_dialog = FinderDialog()

        self.load()
        self.played_games(None)

    def define_emu_combobox(self):
        emulators=config.emulators()
        emulators.insert(0,_("All"))
        liststore = gtk.ListStore(TYPE_STRING)
        for emu in emulators:
            liststore.append([emu])
        self.emu_combobox.set_model(liststore)
        cell = gtk.CellRendererText()
        self.emu_combobox.pack_start(cell, True)
        self.emu_combobox.add_attribute(cell, 'text', 0)
        
        if config.get("General","default_emulator") != None:
            self.emu_combobox.set_active(emulators.index(_(config.get("General","default_emulator"))))
        else:
            self.emu_combobox.set_active(emulators.index(_('All')))
        return

    def define_filter_combobox(self):
        liststore = gtk.ListStore(TYPE_STRING)
        filters=list()
        for filter in config.get("General","filters"):
            label=config.get_title(filter)
            liststore.append([label])
            filters.append(filter)
        self.filter_combobox.clear()
        self.filter_combobox.set_model(liststore)
        cell = gtk.CellRendererText()
        self.filter_combobox.pack_start(cell, True)
        self.filter_combobox.add_attribute(cell, 'text', 0)
        self.last_filter_selection = None
        
        default_filter= config.get("General","default_filter")
        if default_filter and default_filter in filters:
            self.filter_combobox.set_active(filters.index(default_filter))
        else:
            self.filter_combobox.set_active(0)
        return

    def define_filter_treeview(self):
        self.filter_lists=dict()
        for filter in config.get("General","filters"):
            self.filter_lists[filter]=list()
        
        selection=self.filter_treeview.get_selection()
        selection.set_mode(gtk.SELECTION_MULTIPLE)
        selection.select_path(0)

        liststore=gtk.ListStore(TYPE_STRING)
        self.filter_treeview.set_model(liststore)

        for col in self.filter_treeview.get_columns():
            self.filter_treeview.remove_column(col)

        column=gtk.TreeViewColumn()
        self.filter_treeview.append_column(column)
        cell = gtk.CellRendererText()
        column.pack_start(cell, True)
        column.set_attributes(cell, text=0)

        self.filter_treeview.set_enable_search(False)
        self.filter_treeview.set_cursor(0,column)
        return

    def define_games_treeview(self):
        for col in self.games_treeview.get_columns():
            self.games_treeview.remove_column(col)
        self.games_treeview.set_model(None)

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

        cell = gtk.CellRendererPixbuf()
        column= gtk.TreeViewColumn()
        column.pack_start(cell, True)
        self.games_treeview.append_column(column)
        column.set_cell_data_func(cell, 
            lambda c, cell, model, iter: 
            cell.set_property("pixbuf",
                images.get(model.get_value(iter, 1),images['unknown']))) 
        column.set_sort_column_id(1)
        column.set_resizable(False)
        column.set_min_width(30)

        cell = gtk.CellRendererToggle()
        cell.set_property("activatable",True)
        cell.connect("toggled", self.change_favorite)
        column= gtk.TreeViewColumn()
        column.pack_start(cell, True)
        self.games_treeview.append_column(column)
        column.add_attribute(cell,'active',2)
        column.set_sort_column_id(2)
        column.set_resizable(False)
        column.set_min_width(30)

        cell = gtk.CellRendererText()
        for col,name in enumerate(config.get("General","columns")):
            label=config.get_title(name)
            column= gtk.TreeViewColumn(label)
            self.games_treeview.append_column(column)
            column.pack_start(cell, True)
            column.set_attributes(cell, text=col+3)
            column.set_sort_column_id(col+3)
            column.set_resizable(True)
            column.set_min_width(50)
        self.games_treeview.set_reorderable(True)
        self.games_treeview.set_enable_search(True)
        self.games_treeview.set_search_column(1)

        selection=self.games_treeview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        selection.select_path(0)
        return
        
    def define_show_game_zone(self):
        self.game_labels=dict()
        # define the labels that will shown game information
        attrs=pango.AttrList()
        attrs.insert(pango.AttrScale(pango.SCALE_XX_LARGE,0,100))
        attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD,0,100))
        
        for child in self.titles_box.get_children():
            self.titles_box.remove(child)
        self.titles_box.pack_start(gtk.Label(),True,True,0)
        for prop in config.get("General","show"):
            self.game_labels[prop]=gtk.Label()
            self.game_labels[prop].set_attributes(attrs)
            self.game_labels[prop].set_alignment(1,0)
            self.titles_box.pack_start(self.game_labels[prop],False,True,0)
        self.titles_box.pack_start(gtk.Label(),True,True,0)
        self.titles_box.show_all()

        self.tempfile=mkstemp()[1]
        return   

    def add_image_sets(self):     
        #
        # assign tuples to every image path.
        #  zip file tuple  => ( ZipFile class , set_image_from_zipfile function )
        #  directory tuple => ( directory     , set_image_from_directory function )
        #
        pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(config.get("General","default_image"),130,130)
        self.image.set_from_pixbuf(pixbuf)
        self.image_emu_sets=dict()
        # this loop allows the progress bar reference in 'get_image_fraction' function
        for id in config.emulators():
            emu=config.emu(id)
            if emu.has_option("General","images"):
                for image_set in emu.get("General","images"):
                    if image_set not in self.image_sets:
                        self.image_sets[image_set]=False
        # load image sets
        for id in config.emulators():
            self.image_emu_sets[id]=list()
            emu=config.emu(id)
            if emu.has_option("General","images"):
                for image_set in emu.get("General","images"):
                    if not self.image_sets[image_set]:
                        if is_zipfile(image_set):
                            self.image_sets[image_set]=(ZipFile(image_set,"r"),self.set_image_from_zipfile)
                        elif isdir(image_set):
                            self.image_sets[image_set]=(image_set,self.set_image_from_directory)
                    self.image_emu_sets[id].append(self.image_sets[image_set])
        self.show_game(self.games_treeview)
        return

    # return the part of images loaded [0,1]
    def get_image_fraction(self):
        loaded=1
        for value in self.image_sets.values():
            if value:
                loaded=loaded+1
        length= len(self.image_sets)
        if length == 0 or length < loaded:
            return 1
        return float(float(loaded)/float(len(self.image_sets)))

    def set_image_from_directory(self,filename,directory):
        path=os.path.join(directory,filename)
        if not os.access(path,os.R_OK):
            return False
        self.image.set_from_file(path)
        return True

    def set_image_from_zipfile(self,filename,zipfile):
        if filename not in zipfile.namelist():
            return False
        data = zipfile.read(filename)
        f = file(self.tempfile,"w")
        f.write(data)
        f.close()
        self.image.set_from_file(self.tempfile)
        return True

    # show current game
    def show_game(self, treeview, *args):
        (model,iter)=treeview.get_selection().get_selected()
        if iter == None:
            return None
        # set label texts
        game=self.gamelist.get(model.get_value(iter,0))
        for prop in config.get("General","show"):
            self.game_labels[prop].set_text(game[prop])
        # define image format
        id=game["emulator"]
        image_file=config.emu(id).get("General","image_file")
        if image_file:
            rx=re.compile("@\((?P<replace>[^)]+)\)")
            mo = re.search(rx,image_file)
            self.filename = re.sub(rx,game[mo.group('replace')],image_file)
        self.current_image_index=-1
        self.next_image(id)
        return

    def change_image(self,widget,*args):
        (model,iter)=self.games_treeview.get_selection().get_selected()
        if iter == None:
            return None
        game=self.gamelist.get(model.get_value(iter,0))
        id=game["emulator"]
        self.next_image(id)
        return

    # set image from image sets
    def next_image(self,id,*args):
        num_sets=len(self.image_emu_sets[id])
        for i in range(num_sets):
            self.current_image_index=(self.current_image_index+1) % num_sets
            image_set, set_image_function = self.image_emu_sets[id][self.current_image_index]
            if set_image_function(self.filename,image_set):
                break
        else:
            pixbuf=gtk.gdk.pixbuf_new_from_file_at_size(config.get("General","default_image"),130,130)
            self.image.set_from_pixbuf(pixbuf)
        return


    def set_games_list(self,match):
        args=[TYPE_INT,TYPE_STRING,TYPE_BOOLEAN]
        for i in config.get("General","columns"):
            args.append(TYPE_STRING)
        liststore = gtk.ListStore(*args)

        selection=self.games_treeview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        selection.select_path(0)
        
        columns=config.get("General","columns")
        for index,game in enumerate(match):
            line=list()
            line.append(index)
            line.append(game['state'])
            line.append(game['favorite'])
            for name in columns:
                line.append(config.get_title(game[name]))
            liststore.append(line)
        
        self.games_treeview.columns_autosize()
        self.games_treeview.set_model(liststore)
        col=self.games_treeview.get_column(3)
        self.games_treeview.set_cursor(0,col)
        self.games_treeview.grab_focus()
        return

    # key_press event
    def key_press(self, widget, key):
        key=gtk.gdk.keyval_name(key.keyval)
        if key == "Right":
            self.filter_selection()
            return True
        elif key == "Left":
            self.filter_treeview.grab_focus()
            return True
        return False

    # row_activated event
    def execute_emu(self, *args):
        (model,iter)=self.games_treeview.get_selection().get_selected()
        if iter == None:
            return None
        game=self.gamelist.get(model.get_value(iter,0))
        self.gamelist.execute(game)
        for col,name in enumerate(config.get("General","columns")):
            model.set(iter,col+3,config.get_title(game[name]))
        self.games_treeview.grab_focus()
        return

    # filter the selection
    def filter_selection(self, widget=None, *args):
        (model,paths)=self.filter_treeview.get_selection().get_selected_rows()
        if paths == self.last_filter_selection:
            self.games_treeview.grab_focus()
            return None
        self.last_filter_selection= paths

        selection=list()
        for selec in paths:
            iter=model.get_iter(selec[0])
            selection.append(model.get_value(iter,0))

        self.gamelist.set_selection(self.current_filter,selection)
        match=self.gamelist.filter(sort='favorite')
        self.set_games_list(match)
        return

    def played_games(self, widget, *args):
        match=self.gamelist.filter({'last':".+"},sort='last')
        self.set_games_list(match)
        self.last_filter_selection= "played"
        return

    def favorites(self, widget, *args):
        match=self.gamelist.filter({'favorite':".+"},sort='time')
        self.set_games_list(match)
        self.last_filter_selection= "favorites"
        return

    def change_favorite(self,widget,path,*args):
        (model,iter)= self.games_treeview.get_selection().get_selected()
        if iter == None:
            return None
        model=self.games_treeview.get_model()
        index = model.get_value(model.get_iter(path),0)
        model[path][2] = self.gamelist.swap_favorite(index)
        return

    def change_emulator(self, widget, *args):
        emulator=self.emu_combobox.get_active_text()
        self.gamelist.set_emulator(emulator)

        if emulator==_('All'):
            emulator='All'
        config.set("General","default_emulator",emulator)
        config.write_configuration()

        (model,paths)=self.games_treeview.get_selection().get_selected_rows()
        if model == None:
            return
        match=self.gamelist.filter()
        self.set_games_list(match)
        return

    # change category type
    def change_filter(self, combobox, *args):
        active_text=self.filter_combobox.get_active_text()
        self.current_filter=config.get_key(active_text)
        liststore=self.filter_treeview.get_model()
        liststore.clear()
        for row in self.filter_lists[self.current_filter]:
            liststore.append([row])
        col=self.filter_treeview.get_column(0)
        self.filter_treeview.set_cursor(0,col)
        self.filter_treeview.grab_focus()
        self.last_filter_selection = None
        config.set("General","default_filter",config.get_key(active_text))
        config.write_configuration()
        return

    # open the find dialog and filter the pattern
    def open_finder_dialog(self, *args):
        response = self.finder_dialog.open()
        if response != gtk.RESPONSE_OK and response != gtk.RESPONSE_APPLY:
            self.finder_dialog.close()
            return
        pattern=self.finder_dialog.get_values()
        match=self.gamelist.filter(pattern,sort='favorite')
        self.set_games_list(match)
        self.last_filter_selection = None
        if response == gtk.RESPONSE_APPLY:
            self.open_finder_dialog()
        self.finder_dialog.close()
        return

    def open_preferences_dialog(self, widget, *args):
        bak_config=Configuration()
        bak_config.set_dirs(config.get_dirs())
        bak_config.load()
        self.preferences_dialog.open()
        response=gtk.RESPONSE_APPLY
        while response == gtk.RESPONSE_APPLY:
            response = self.preferences_dialog.preferences_dialog.run()
            if response in [gtk.RESPONSE_OK,gtk.RESPONSE_APPLY]:
                self.preferences_dialog.update_configuration()
        self.preferences_dialog.close()
        while gtk.events_pending():
            gtk.main_iteration_do(False)
        config.load()
        
        self.last_filter_selection=None
        if bak_config.get("General","finders") != config.get("General","finders"):
            self.finder_dialog.build()
        if bak_config.get("General","columns") != config.get("General","columns"):
            self.define_games_treeview()
        if bak_config.get("General","show") != config.get("General","show"):
            self.define_show_game_zone()
            self.show_game(self.games_treeview)
        if bak_config.get("General","filters") != config.get("General","filters"):
            self.define_filter_treeview()
            self.define_filter_combobox()
            self.filter_lists=self.gamelist.get_filter_lists()
            self.change_filter(self.filter_combobox)
        
        self.load()
        return

    def load(self):
        self.loading_dialog = LoadingDialog()
        self.loading_dialog.loading_dialog.set_transient_for(self.main_window)
        self.loading_dialog.progressbar.set_pulse_step(0.05)

        self.loading_dialog.loading_dialog.show()
        while gtk.events_pending():
            gtk.main_iteration_do(False)
        self.loading_in_progress=True
        self.get_fraction= lambda *a: 0.0001
        start_new_thread(self.load_process,())
        while self.loading_in_progress:
            fraction=self.get_fraction()
            if fraction:
                self.loading_dialog.progressbar.set_fraction(fraction)
            else:
                self.loading_dialog.progressbar.pulse()
            while gtk.events_pending():
                gtk.main_iteration_do(False)
            sleep(0.1)
        self.loading_dialog.loading_dialog.hide()
        return

    def load_process(self,*args):
        self.get_fraction=self.get_image_fraction
        self.loading_dialog.progressbar.set_text(_("Loading Images ..."))
        self.add_image_sets()
        if not self.preferences_dialog.gamelist_is_rebuilded():
            self.loading_in_progress=False
            return
        if not os.access(config.get("General","gamelist"),os.R_OK):
            self.loading_in_progress=False
            return
        self.get_fraction=self.gamelist.get_loader_fraction
        self.loading_dialog.progressbar.set_text(_("Loading Gamelist ..."))
        err = self.gamelist.load()
        if err:
            dlg=gtk.MessageDialog(self.main_window, gtk.DIALOG_MODAL,gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,err)
            dlg.run()
            dlg.destroy()
        self.get_fraction= self.gamelist.get_filter_fraction
        self.loading_dialog.progressbar.set_text(_("Loading Filters ..."))
        self.filter_lists=self.gamelist.get_filter_lists()
        self.change_filter(self.filter_combobox)
        self.loading_in_progress=False
        return

class LoadingDialog(SimpleGladeApp):

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

class FinderDialog(SimpleGladeApp):

    def __init__(self, path="Loemu.glade",
                 root="finder_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.build()

    def build(self):
        for child in self.titles_box.get_children():
            self.titles_box.remove(child)
        for child in self.entries_box.get_children():
            self.entries_box.remove(child)
        self.filter= dict()
        for entry in config.get("General","finders"):
            label=gtk.Label(config.get_title(entry))
            label.set_alignment(1,0)
            self.titles_box.pack_start(label, False, True, 6)
        
            self.filter[entry]=gtk.Entry()
            self.filter[entry].set_max_length(50)
            self.filter[entry].select_region(0, len(self.filter[entry].get_text()))
            self.entries_box.pack_start(self.filter[entry], False, True, 1)
        self.entries_box.show_all()
        self.titles_box.show_all()

    def open(self):
        self.finder_dialog.show()
        return self.finder_dialog.run()

    def close(self):
        self.finder_dialog.hide()
        return

    def get_values(self):
        pattern=dict()
        for entry in self.filter:
            pattern[entry]=self.filter[entry].get_text()
        return pattern
