#!/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 _widgets
from _choicebars import MainMenu, MainToolbar
from _dialogs import MacroDialog, CsvImportDialog, DimensionsEntryDialog, \
                     AboutDialog
from _interfaces import CsvImport, string_match
from config import DEBUG, LIBPREFIX, icon_size

# begin wxGlade: extracode
# end wxGlade


import distutils.sysconfig
try:
    testfile = open(LIBPREFIX + "icons/pyspread.png",'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)

        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(LIBPREFIX+'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):
        ##print event.GetKeyCode()
        # If Ctrl+X is pressed...
        if event.ControlDown() and event.GetKeyCode() == 88:
            if DEBUG: print "Ctrl+X"
            # Call copy method
            self.OnCut()
        
        # If Ctrl+C is pressed...
        if not event.ShiftDown() and event.ControlDown() and event.GetKeyCode() == 67:
            if DEBUG: print "Ctrl+C"
            # Call copy method
            self.OnCopy()
        
        # If Shift+Ctrl+C is pressed...
        if event.ShiftDown() and event.ControlDown() and event.GetKeyCode() == 67:
            if DEBUG: print "Shift+Ctrl+C"
            # Call copy method
            self.OnCopyResult()
        
        # If Ctrl+V is pressed...
        if event.ControlDown() and event.GetKeyCode() == 86:
            if DEBUG: print "Ctrl+V"
            # Call paste method
            self.OnPaste()
            
        # If Supr is presed
        if event.GetKeyCode() == 127:
            if DEBUG: print "Supr"
            # Call delete method
            self.MainGrid.delete()
            
        # 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 > 1E7:
                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)
        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)
