#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright 2008 Martin Manns
# Distributed under the terms of the GNU General Public License
# generated by wxGlade 0.6 on Mon Mar 17 23:22:49 2008

"""
pyspread
=======

Pyspread is a 3D spreadsheet application. 

Each cell accepts a Python expression and returns an accessible object. 
Python modules are usable from the spreadsheet table without external 
scripts.

Start from command line
pyspread.py

or from the Python shell:
>>> import pyspread
>>> pyspread.Pyspread()

Requires:
Python >= 2.4
Numpy >= 1.1.0
wxPython >= 2.8.7.1.

Examples require:
gmpy
rpy

--------------------------------------------------------------------
pyspread is distributed under the terms of the GNU General Public
License:

pyspread is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

pyspread 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 pyspread.  If not, see <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------

"""

import bz2
import os
import sys
import csv
import cPickle as pickle

import numpy
import wx
import wx.grid
import wx.lib.printout as printout

import pyspread._widgets as _widgets
from pyspread._choicebars import MainMenu, MainToolbar
from pyspread._dialogs import MacroDialog, CsvImportDialog, \
                       DimensionsEntryDialog, AboutDialog
from pyspread._interfaces import CsvImport, string_match
from pyspread.config import DEBUG, LIBPREFIX, ICONPREFIX, icon_size, keyfuncs

# begin wxGlade: extracode
# end wxGlade


import distutils.sysconfig
try:
    testfile = open(LIBPREFIX + "_datastructures.py",'r')
    testfile.close()
except IOError:
    LIBPREFIX = distutils.sysconfig.get_python_lib() + '/pyspread/'


class MainWindow(wx.Frame):
    def __init__(self, *args, **kwds):
        try:
            dim = kwds.pop("dimensions")
        except:
            dim = (1000, 100, 1)
        self.wildcard = "Pyspread file (*.pys)|*.pys|"     \
                        "All files (*.*)|*.*"
                        
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        
        # Menu Bar
        self.main_window_menubar = wx.MenuBar()
        self.main_menu = MainMenu(parent=self, menubar=self.main_window_menubar)
        self.SetMenuBar(self.main_window_menubar)
        
        # Status bar
        self.main_window_statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
        
        # Tool Bar
        self.main_window_toolbar = MainToolbar(self, -1)
        self.SetToolBar(self.main_window_toolbar)
        
        # Main grid
        self.MainGrid = _widgets.MainGrid(self, -1, size=(1, 1), dim=dim, \
                        SetStatusText=self.main_window_statusbar.SetStatusText)

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_FIND, self.OnFind)
        self.Bind(wx.EVT_FIND_NEXT, self.OnFind)
        self.Bind(wx.EVT_FIND_REPLACE, self.OnFind)
        self.Bind(wx.EVT_FIND_REPLACE_ALL, self.OnFind)
        self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
        wx.EVT_KEY_DOWN(self.MainGrid, self.OnKey)
        
        # Misc
        self.MainGrid.mainwindow = self
        self.MainGrid.deletion_imminent = False
           
    def __set_properties(self):
        self.SetTitle("pyspread")
        _icon = wx.EmptyIcon()
        _icon.CopyFromBitmap(wx.Bitmap(ICONPREFIX+'icons/pyspread.png', wx.BITMAP_TYPE_ANY))
        self.SetIcon(_icon)
        self.SetSize((1000, 700))
        self.main_window_statusbar.SetStatusWidths([-1])
        
        # statusbar fields
        main_window_statusbar_fields = [""]
        for i in range(len(main_window_statusbar_fields)):
            self.main_window_statusbar.SetStatusText(main_window_statusbar_fields[i], i)
        self.main_window_toolbar.SetToolBitmapSize(icon_size)
        self.main_window_toolbar.SetMargins((1, 1))
        self.main_window_toolbar.Realize()
        # end wxGlade
        self.MainGrid.create_rowcol()
        
    def __do_layout(self):
        # begin wxGlade: MainWindow.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_3 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_2 = wx.FlexGridSizer(1, 1, 0, 0)
        sizer_3.Add(self.MainGrid.entry_line, 0, wx.EXPAND, 0)
        grid_sizer_2.Add(self.MainGrid, 0, wx.EXPAND, 0)
        grid_sizer_2.AddGrowableRow(0)
        grid_sizer_2.AddGrowableCol(0)
        sizer_3.Add(grid_sizer_2, 1, wx.EXPAND, 0)
        sizer_2.Add(sizer_3, 1, wx.EXPAND, 0)
        sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)
        self.Layout()
        # end wxGlade
        
    def OnKey(self, event):
        """
        Additional key behavior if not already in menu
        
        The method parses the key representation and calls the respective
        function for the defined keys. It relies on the keyfuncs dict in 
        config.py and on self.key_modifier_methods. Note that menu defined keys
        are not evaluated.
        
        """
        
        # Key modifier event methods
        kmm = {'Ctrl': 'event.ControlDown()', \
               'not_Ctrl': 'not event.ControlDown()', \
               'Shift': 'event.ShiftDown()', \
               'not_Shift': 'not event.ShiftDown()'}
               
        for keystr, funcstr in keyfuncs.iteritems():
            ks = keystr.split('+')
            actkey = ks.pop() # Actual unmodified key in keyfuncs
            evtmeths = [kmm[modifier] for modifier in ks]
            evtmeths += [" == ".join(["event.GetKeyCode()", repr(ord(actkey))])]
            evtmeths_string = " and ".join(evtmeths)
            if eval(evtmeths_string):
                funccallstr = "".join(["self.", funcstr, "()"])
                eval(funccallstr)
                
        # Skip other Key events
        if event.GetKeyCode():
            event.Skip()
        
    def OnFileNew(self, event): # wxGlade: MainWindow.<event_handler>
        dim_dialog = DimensionsEntryDialog(self)
        if dim_dialog.ShowModal() == wx.ID_OK:
            dim = dim_dialog.dimensions
            for i, d in enumerate(dim):
                if d <= 0: dim[i] = 1
            no_gridcells = reduce(lambda x, y: x*y, dim)
            if no_gridcells > 1E6:
                dlg = wx.MessageDialog(self, 'The grid has ' + \
                                       str(no_gridcells) + ' cells.\n' + \
                                       'You may run out of memory.\n' + \
                                       'Do you want to continue?',
                                       'Memory alert', 
                                       wx.OK | wx.CANCEL | wx.ICON_INFORMATION)
                res = dlg.ShowModal()
                if res != wx.ID_OK:
                    dim_dialog.Destroy()
                    event.Skip()
                    return 0
                dlg.Destroy()
            dimensions = tuple(dim)
            if DEBUG: print dimensions
            self.Destroy()
            self = MainWindow(None, dimensions=dimensions)
            self.Show()
        dim_dialog.Destroy()
        self.MainGrid.pysgrid.unredo.reset()
        event.Skip()

    def OnFileOpen(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnFileOpen'"
        dlg = wx.FileDialog(
            self, message="Choose a file", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=self.wildcard, \
            style=wx.OPEN | wx.CHANGE_DIR)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.MainGrid.loadfile(path)
            self.MainGrid.OnCombo(event)
        event.Skip()

    def OnFileSave(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnFileSave'"
        dlg = wx.FileDialog(
            self, message="Save file as ...", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=self.wildcard, style=wx.SAVE
            )
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            print path
            outfile = bz2.BZ2File(path,"wb")
            pickle.dump(self.MainGrid.pysgrid.sgrid, outfile, protocol=2)
        dlg.Destroy()        
        event.Skip()

    def OnFilePrint(self, event): # wxGlade: MainWindow.<event_handler>
        ### UGLY CODE HERE, WE NEED A BRUSH-UP ###
        if DEBUG: print "Event handler `OnFilePrint'"
        selection = self.MainGrid.get_selection()
        rowslice, colslice = self.MainGrid.get_selected_rows_cols(selection)
        data = self.MainGrid.getselectiondata(self.MainGrid.pysgrid, \
                                              rowslice, colslice, omittedfield_repr=' ')
        prt = printout.PrintTable(self.MainGrid)
        prt.data = data
        prt.Print()
        event.Skip()

    def OnExit(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnExit'"
        sys.exit()
        event.Skip()

    def OnCut(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnCut'"
        self.MainGrid.cut()
        self.MainGrid.pysgrid.unredo.mark()
        event.Skip()

    def OnCopy(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnCopy'"
        self.MainGrid.copy(source=self.MainGrid.pysgrid.sgrid)
        self.MainGrid.pysgrid.unredo.mark()
        event.Skip()

    def OnCopyResult(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnCopyResult'"
        self.MainGrid.copy(source=self.MainGrid.pysgrid)
        self.MainGrid.pysgrid.unredo.mark()
        event.Skip()

    def OnPaste(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnPaste'"
        self.MainGrid.paste()
        self.MainGrid.pysgrid.unredo.mark()
        event.Skip()

    def OnAbout(self, event): # wxGlade: MainWindow.<event_handler>
        about_dialog = AboutDialog(self)
        about_dialog.ShowModal()
        about_dialog.Destroy()
        event.Skip()

    def OnInsertRows(self, event): # wxGlade: MainWindow.<event_handler>
        """Insert the maximum of 1 and the number of selected rows""" 
        if DEBUG: print "Event handler `OnInsertRows'"
        self.MainGrid.insert_rows()
        event.Skip()

    def OnInsertColumns(self, event): # wxGlade: MainWindow.<event_handler>
        """Inserts the maximum of 1 and the number of selected columns """
        if DEBUG: print "Event handler `OnInsertColumns'"
        self.MainGrid.insert_cols()
        event.Skip()

    def OnInsertTable(self, event): # wxGlade: MainWindow.<event_handler>
        """Insert one table into MainGrid and pysgrid """ 
        if DEBUG: print "Event handler `OnInsertTable'"
        self.MainGrid.insert_tables()
        event.GetString = lambda x=0: str(self.MainGrid.current_table)
        self.MainGrid.OnCombo(event)
        event.Skip()

    def OnDeleteRows(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnDeleteRows'"
        self.MainGrid.delete_rows()
        event.Skip()
        
    def OnDeleteColumns(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnDeleteColumns'"
        self.MainGrid.delete_cols()
        event.Skip()

    def OnDeleteTable(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnDeleteTable'"
        self.MainGrid.delete_tables()
        event.GetString = lambda x=0: \
              str(max(0, self.MainGrid.current_table, \
                         self.MainGrid.pysgrid.sgrid.shape[2]-1))
        self.MainGrid.OnCombo(event)
        event.Skip()

    def OnCSVImport(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnCSVImport'"
        csvwildcard = " CSV file|*.*|Tab-delimited text file|*.*"
        path = None
        # File choice
        filedlg = wx.FileDialog(
            self, message="Import a csv-file", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=csvwildcard, style=wx.OPEN | wx.CHANGE_DIR
            )
        if filedlg.ShowModal() == wx.ID_OK:
            path = filedlg.GetPath()
            filedlg.Destroy()
        
        # CSV import option choice
        if path is None:
            return 0 
        filterdlg = CsvImportDialog(self, csvfilepath=path)
        if filedlg.GetFilterIndex() == 0:
            if filterdlg.ShowModal() == wx.ID_OK:
                csvargs = filterdlg.get_csvargs()
            else:
                filterdlg.Destroy()
                return 0                
        elif filedlg.GetFilterIndex() == 1:
            csvargs = {'dialect': 'excel-tab', 'quoting': csv.QUOTE_NONNUMERIC}
        
        # The actual data import
        csv_interface = CsvImport(path, csvargs)
        topleftcell = tuple(list(self.MainGrid.get_currentcell()) + \
                            [self.MainGrid.current_table])
        csv_interface.fill_target(self.MainGrid.pysgrid, key=topleftcell)
        self.MainGrid.update_grid(funceval=True, gridslice=None)
        event.Skip()

    def OnMacroList(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnMacroList'"
        #print self.MainGrid.pysgrid.macros
        dlg = MacroDialog(None, -1, "", macros = self.MainGrid.pysgrid.macros)
        if dlg.ShowModal() == wx.ID_OK:
            # Insert function string into current cell
            targetcell = self.MainGrid.get_currentcell()
            macrostring = dlg.GetMacroString()
            try:
                self.MainGrid.entry_line.SetValue(macrostring)
            except TypeError: 
                self.MainGrid.entry_line.SetValue("")
            self.MainGrid.pysgrid[targetcell] = macrostring
            self.MainGrid.update_grid(funceval=True)
        self.MainGrid.pysgrid.macros = dlg.macros
        self.MainGrid.pysgrid.set_global_macros(self.MainGrid.pysgrid.macros)
        dlg.Destroy()
        self.MainGrid.update_grid()
        event.Skip()

    def OnMacroListLoad(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnMacroListLoad'"
        macrowildcard = " Macro file|*.*"
        # File choice
        filedlg = wx.FileDialog(
            self, message="Load a Macro-file", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=macrowildcard, \
            style=wx.OPEN | wx.CHANGE_DIR)
        if filedlg.ShowModal() == wx.ID_OK:
            path = filedlg.GetPath()
            filedlg.Destroy()
        macrocodes = {}
        infile = bz2.BZ2File(path, "r")
        macrocodes = pickle.load(infile)
        infile.close()
        #print macrocodes
        for macroname in macrocodes:
            self.MainGrid.pysgrid.macros.AddToMacroDict(macrocodes[macroname])
        self.MainGrid.pysgrid.set_global_macros()
        event.Skip()

    def OnMacroListSave(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnMacroListSave'"
        macrowildcard = " Macro file|*.*"
        # File choice
        filedlg = wx.FileDialog(
            self, message="Save a Macro-file", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=macrowildcard, \
            style=wx.OPEN | wx.CHANGE_DIR)
        if filedlg.ShowModal() == wx.ID_OK:
            path = filedlg.GetPath()
            filedlg.Destroy()
        macros = self.MainGrid.pysgrid.macros
        macrocodes = dict((m, macros[m].func_dict['macrocode']) for m in macros)
        outfile = bz2.BZ2File(path,"w")
        pickle.dump(macrocodes, outfile, protocol=2)
        outfile.close()
        event.Skip()
    
    def OnFind(self, event): # wxGlade: MainWindow.<event_handler>
        ### Find functionality should be in interfaces
        if DEBUG: print "Event handler `OnFind'"
        wx_map = { wx.wxEVT_COMMAND_FIND : "FIND",
                   wx.wxEVT_COMMAND_FIND_NEXT : "FIND_NEXT",
                   wx.wxEVT_COMMAND_FIND_REPLACE : "REPLACE",
                   wx.wxEVT_COMMAND_FIND_REPLACE_ALL : "REPLACE_ALL" }
        wx_flags = { 0: ["UP", ],
                     1: ["DOWN"], 
                     2: ["UP", "WHOLE_WORD"], 
                     3: ["DOWN", "WHOLE_WORD"], 
                     4: ["UP", "MATCH_CASE"], 
                     5: ["DOWN", "MATCH_CASE"], 
                     6: ["UP", "WHOLE_WORD", "MATCH_CASE"], 
                     7: ["DOWN", "WHOLE_WORD", "MATCH_CASE"] }
        et = event.GetEventType()
        ef = event.GetFlags()        
        try:
            event_type = wx_map[et]
            event_flags = wx_flags[ef]
        except KeyError:
            if DEBUG: print "Unknown event type or flag:", et, ef
            return 0            
        event_find_string = event.GetFindString()
        event_replace_string = event.GetReplaceString()
        
        findpos = self.MainGrid.pysgrid.findnextmatch(self.MainGrid.key, \
                                            event_find_string, event_flags)
        if event_type in ["FIND", "FIND_NEXT", "REPLACE"]:
            if findpos is not None:
                self.MainGrid.selectnewcell(findpos, event)
                self.main_window_statusbar.SetStatusText("Found '" + \
                                 event_find_string +  "' in cell " + \
                                 str(list(findpos)) + ".", 0)
            else:
                self.main_window_statusbar.SetStatusText("'" + \
                                 event_find_string + "' not found.", 0)
        if event_type in ["REPLACE", "REPLACE_ALL"]:
            noreplaced = 0
            while findpos is not None:
                noreplaced += 1
                cellstring = self.MainGrid.pysgrid.sgrid[findpos]
                string_position = string_match(cellstring, \
                                    event_find_string, event_flags)
                newstring = cellstring[:string_position]
                newstring += cellstring[string_position:].replace(\
                                       event_find_string, \
                                       event_replace_string, 1)
                self.MainGrid.pysgrid.sgrid[findpos] = newstring
                if event_type == "REPLACE":
                    self.main_window_statusbar.SetStatusText("'" + 
                                 cellstring + "' replaced by '" + \
                                 newstring + "'.", 0)
                    break
                elif event_type == "REPLACE_ALL":
                    findpos = self.MainGrid.pysgrid.findnextmatch( \
                                 findpos, event_find_string, event_flags)
                else: 
                    raise ValueError, "Event type " + event_type + " unknown."
            self.MainGrid.update_grid()
            #self.__fill_cells()
            if event_type == "REPLACE_ALL":
                self.main_window_statusbar.SetStatusText(str(noreplaced) + \
                                 " occurrences of '" + event_find_string + \
                                 "' replaced by '" +  event_replace_string + \
                                 "'.", 0)
            else:
                self.MainGrid.entry_line.SetValue(newstring)
        event.Skip()

    def OnFindClose(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnFindClose'"
        event.GetDialog().Destroy()
        self.main_window_statusbar.SetStatusText("", 0)
        event.Skip()


    def OnUndo(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnUndo'"
        self.MainGrid.undo()
        event.Skip()

    def OnRedo(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnRedo'"
        self.MainGrid.redo()
        event.Skip()

    def OnShowFind(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnShowFind'"
        data = wx.FindReplaceData()
        dlg = wx.FindReplaceDialog(self, data, "Find")
        dlg.data = data  # save a reference to it...
        dlg.Show(True)        
        event.Skip()

    def OnShowFindReplace(self, event): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnShowFindReplace'"
        data = wx.FindReplaceData()
        dlg = wx.FindReplaceDialog(self, data, "Find & Replace", \
                                   wx.FR_REPLACEDIALOG)
        dlg.data = data  # save a reference to it...
        dlg.Show(True)        
        event.Skip()

# end of class MainWindow

def __main__(filename=None, dimensions=(1000, 100, 3)):
    """
    Starts pyspread with file 'filename' or with empty grid
    
    Parameters
    ----------
    filename: String defaults to None
    \tfilename of the pyspread file that is initially loaded
    dimensions: 3-tuple
    \tDimensions of empty grid (i.e. if filename is None) (rows, cols, tables)
    
    """
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    main_window = MainWindow(None, dimensions=dimensions)
    if filename is not None:
        main_window.MainGrid.loadfile(filename)
    app.SetTopWindow(main_window)
    main_window.Show()
    app.MainLoop()

if __name__ == "__main__":
    #cProfile.run('main()')
    try:
        filename = sys.argv[1]
    except IndexError:
        filename = None    
    __main__(filename)
