'''
 ====================================================================
 Copyright (c) 2003-2006 Barry A Scott.  All rights reserved.

 This software is licensed as described in the file LICENSE.txt,
 which you should have received as part of this distribution.

 ====================================================================

    wb_subversion_history.py

'''
import wx
import wx.lib.splitter
import wx.calendar

import tempfile
import time
import types

import pysvn

import wb_images
import wb_subversion_utils
import wb_subversion_diff

id_view_cmd = wx.NewId()
id_diff_cmd = wx.NewId()
id_list = wx.NewId()
id_paths = wx.NewId()

action_map = {
    'A': 'Add',
    'D': 'Delete',
    'M': 'Modified',
    }

class LogEntry:
    def __init__( self,
            rev_number,
            url,
            author,
            date,
            label,
            message,
            changed_paths ):
        self.rev_number = rev_number
        self.url = url
        self.author = author
        self.date = date
        self.label = label
        self.message = message
        self.changed_paths = changed_paths

def getHistoryEntries( project_info, filename, limit, revision_end ):
    history_entries = []
    # need the URL and repos_root_URL
    # [0] first entry [0][1] the info
    info = project_info.client_bg.info2( filename, recurse=False )[0][1]
    if info.repos_root_URL is None:
        info = project_info.client_bg.info2( info.URL, recurse=False )[0][1]

    log_entries = project_info.client_bg.log(
                    filename,
                    strict_node_history=False,
                    discover_changed_paths=True,
                    limit=limit,
                    revision_end=revision_end )

    repos_path = info.URL[len(info.repos_root_URL):]
    for log in log_entries:
        history_entries.append(
            LogEntry( log.revision.number,
                info.repos_root_URL+repos_path,
                log.author,
                log.date,
                '',
                log.message,
                log.changed_paths ) )

        for changed_path in log.changed_paths:
            if changed_path.action in ['A','M']:
                if repos_path == changed_path.path:
                    if changed_path.copyfrom_path is not None:
                        repos_path = changed_path.copyfrom_path
                    break

    tags_url = project_info.getTagsUrl( info.URL )
    if tags_url:
        for ls_info in project_info.client_bg.ls( tags_url ):
            history_entries.append( LogEntry(ls_info.created_rev.number,
                '',
                ls_info.last_author,
                ls_info.time,
                'Tag ' + ls_info.name.split('/')[-1],
                '',
                []) )

    history_entries.sort()
    return info.URL, history_entries

class HistoryDialog(wx.Dialog):
    def __init__( self, parent ):
        wx.Dialog.__init__( self, parent, -1, 'Log History' )

        self.g_sizer = wx.FlexGridSizer( 0, 2, 0, 0 )
        self.g_sizer.AddGrowableCol( 1 )

        self.all_radio = wx.RadioButton( self, -1, 'Show all entries' )
        self.all_radio.SetValue( True )
        self.g_sizer.Add( self.all_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )
        self.g_sizer.Add( (0,0), 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

        self.limit_radio = wx.RadioButton( self, -1, 'Show only:' )
        self.limit_text = wx.TextCtrl( self, -1, '20', style=wx.TE_RIGHT )
        self.limit_text.Enable( False )
        self.g_sizer.Add( self.limit_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )
        self.g_sizer.Add( self.limit_text, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

        self.date = wx.DateTime_Now()
        self.date.SubtractDS( wx.DateSpan( weeks=1 ) )

        self.since_radio = wx.RadioButton( self, -1, 'Show since:' )
        self.since_date = wx.calendar.CalendarCtrl( self, -1,
                                self.date,
                                style=wx.calendar.CAL_MONDAY_FIRST )
        self.since_date.Enable( False )
        self.g_sizer.Add( self.since_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_TOP, 3 )
        self.g_sizer.Add( self.since_date, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

        self.button_ok = wx.Button( self, wx.ID_OK, ' OK ' )
        self.button_ok.SetDefault()
        self.button_cancel = wx.Button( self, wx.ID_CANCEL, ' Cancel ' )

        self.h_sizer_buttons = wx.BoxSizer( wx.HORIZONTAL )
        self.h_sizer_buttons.Add( (150, 20), 1, wx.EXPAND )
        self.h_sizer_buttons.Add( self.button_ok, 0, wx.EXPAND|wx.EAST, 15 )
        self.h_sizer_buttons.Add( self.button_cancel, 0, wx.EXPAND|wx.EAST, 2 )

        self.v_sizer = wx.BoxSizer( wx.VERTICAL )
        self.v_sizer.Add( self.g_sizer, 0, wx.EXPAND|wx.ALL, 5 )
        self.v_sizer.Add( self.h_sizer_buttons, 0, wx.EXPAND|wx.ALL, 5 )

        self.SetAutoLayout( True )
        self.SetSizer( self.v_sizer )
        self.v_sizer.Fit( self )
        self.Layout()

        self.CentreOnParent()

        wx.EVT_BUTTON( self, wx.ID_OK, self.OnOk )
        wx.EVT_BUTTON( self, wx.ID_CANCEL, self.OnCancel )

        wx.EVT_RADIOBUTTON( self, self.all_radio.GetId(), self.OnRadio )
        wx.EVT_RADIOBUTTON( self, self.limit_radio.GetId(), self.OnRadio )
        wx.EVT_RADIOBUTTON( self, self.since_radio.GetId(), self.OnRadio )

        wx.calendar.EVT_CALENDAR_SEL_CHANGED( self, self.since_date.GetId(), self.OnCalendarSelChanged )

    # ----------------------------------------
    def OnOk( self, event ):
        self.EndModal( wx.ID_OK )

    def OnCancel( self, event ):
        self.EndModal( wx.ID_CANCEL )

    # ----------------------------------------
    def OnRadio( self, event ):
        self.since_date.Enable( self.since_radio.GetValue() )
        self.limit_text.Enable( self.limit_radio.GetValue() )

    def OnCalendarSelChanged( self, event ):
        if self.since_radio.GetValue():
            # ensure that the date stays in the past
            date = self.since_date.GetDate()
            # sometimes the event is sent with a bogus value for the date
            # just ignore these events
            if date.GetTicks() == ((2**32)-1):
                return
            if date.IsLaterThan( wx.DateTime_Now() ):
                self.since_date.SetDate( self.date )
            else:
                self.date = self.copyDataTime( date )
        else:
            # CalendarCtrl does not disable day changes
            # force date back to self.date
            self.since_date.SetDate( self.date )

    def copyDataTime( self, date ):
        t = date.GetTicks()
        return wx.DateTimeFromTimeT( t )

    # ----------------------------------------
    def getLimit( self ):
        if self.limit_radio.GetValue():
            try:
                return int( self.limit_text.GetValue().strip() )
            except ValueError:
                return 10
        else:
            return 0

    def getRevisionEnd( self ):
        if self.since_radio.GetValue():
            date = self.since_date.GetDate()
            return pysvn.Revision( pysvn.opt_revision_kind.date, date.GetTicks() )
        else:
            return pysvn.Revision( pysvn.opt_revision_kind.number, 0 )

class HistoryFileFrame(wx.Frame):
    def __init__( self, app, project_info, filename, url, log_entries ):
        wx.Frame.__init__( self, None, -1, "History of %s" % filename, size=(700,500) )

        self.panel = LogHistoryPanel( self, app, project_info, filename, url, log_entries )

        # Set the application icon
        self.SetIcon( wb_images.getIcon( 'wb.png' ) )

        wx.EVT_CLOSE( self, self.OnCloseWindow )

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

    def diffFunction( self, app, project_info, info1, info2 ):
        return wb_subversion_diff.subversionDiffFiles(
                    app, project_info,
                    info1, info2 )

class HistoryDirFrame(wx.Frame):
    def __init__( self, app, project_info, filename, url, log_entries ):
        wx.Frame.__init__( self, None, -1, "History of %s" % filename, size=(700,500) )

        self.panel = LogHistoryPanel( self, app, project_info, filename, url, log_entries )

        # Set the application icon
        self.SetIcon( wb_images.getIcon( 'wb.png' ) )

        wx.EVT_CLOSE( self, self.OnCloseWindow )

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

    def diffFunction( self, app, project_info, info1, info2 ):
        return wb_subversion_diff.subversionDiffDir(
                    app, project_info,
                    info1, info2 )

class LogHistoryPanel:
    col_revision = 0
    col_author = 1
    col_date = 2
    col_label = 3
    col_message = 4

    col_action = 0
    col_path = 1
    col_copyfrom_revision = 2
    col_copyfrom_path = 3

    def __init__( self, parent, app, project_info, filename, url, log_entries ):
        self.parent = parent
        self.app = app
        self.project_info = project_info
        self.filename = filename
        self.url = url
        self.log_entries = log_entries

        # Create the splitter windows
        self.splitter = wx.lib.splitter.MultiSplitterWindow( parent )
        self.splitter.SetOrientation( wx.VERTICAL )

        # Make sure the splitters can't be removed by setting a minimum size
        self.splitter.SetMinimumPaneSize( 100 )

        # create the individule panels
        self.panel_history = wx.Panel( self.splitter, -1 )

        self.panel_comment = wx.Panel( self.splitter, -1 )
        self.panel_changed_paths = wx.Panel( self.splitter, -1 )

        # Arrange the panels with the splitter windows
        self.splitter.AppendWindow( self.panel_history, 250 )
        self.splitter.AppendWindow( self.panel_comment, 100 )
        self.splitter.AppendWindow( self.panel_changed_paths, 150 )

        # run from recent to old
        self.log_entries.sort( self.cmpLogEntries )

        self.selected_revisions = {}

        self.v_sizer_history = wx.BoxSizer( wx.VERTICAL )
        self.v_sizer_comment = wx.BoxSizer( wx.VERTICAL )
        self.v_sizer_changed_paths = wx.BoxSizer( wx.VERTICAL )

        self.list_ctrl = wx.ListCtrl( self.panel_history, id_list, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT|wx.NO_BORDER)

        self.list_ctrl.InsertColumn( self.col_revision, "Revision" )
        self.list_ctrl.InsertColumn( self.col_author, "Author" )
        self.list_ctrl.InsertColumn( self.col_date, "Date" )
        self.list_ctrl.InsertColumn( self.col_label, "Label" )
        self.list_ctrl.InsertColumn( self.col_message, "Message" )

        char_width = 9
        self.list_ctrl.SetColumnWidth( self.col_revision, 7*char_width )
        self.list_ctrl.SetColumnWidth( self.col_author, 14*char_width )
        self.list_ctrl.SetColumnWidth( self.col_date, 15*char_width )
        self.list_ctrl.SetColumnWidth( self.col_label, 12*char_width )
        self.list_ctrl.SetColumnWidth( self.col_message, 40*char_width )

        # don't make the ctrl readonly as that prevents copy as well as insert
        self.comment_ctrl = wx.TextCtrl( self.panel_comment, -1, '', size=wx.Size(-1, -1), style=wx.TE_MULTILINE )
        self.comment_ctrl.SetInsertionPoint( 0 )

        self.paths_ctrl = wx.ListCtrl( self.panel_changed_paths, id_paths, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT|wx.NO_BORDER)

        self.paths_ctrl.InsertColumn( self.col_action, "Action" )
        self.paths_ctrl.InsertColumn( self.col_path, "Path" )
        self.paths_ctrl.InsertColumn( self.col_copyfrom_revision, "Copied Revision" )
        self.paths_ctrl.InsertColumn( self.col_copyfrom_path, "Copied from" )

        char_width = 9
        self.paths_ctrl.SetColumnWidth( self.col_action, 7*char_width )
        self.paths_ctrl.SetColumnWidth( self.col_path, 40*char_width )
        self.paths_ctrl.SetColumnWidth( self.col_copyfrom_revision, 6*char_width )
        self.paths_ctrl.SetColumnWidth( self.col_copyfrom_path, 40*char_width )

        self.initButtons( self.v_sizer_history )

        self.comment_label = wx.StaticText( self.panel_comment, -1, 'Comment' )

        self.paths_label = wx.StaticText( self.panel_changed_paths, -1, 'Changed Paths' )

        self.v_sizer_history.Add( self.list_ctrl, 2, wx.EXPAND|wx.ALL, 5 )
        self.v_sizer_comment.Add( self.comment_label, 0, wx.ALL, 5 )
        self.v_sizer_comment.Add( self.comment_ctrl, 2, wx.EXPAND|wx.ALL, 5 )
        self.v_sizer_changed_paths.Add( self.paths_label, 0, wx.ALL, 5 )
        self.v_sizer_changed_paths.Add( self.paths_ctrl, 2, wx.EXPAND|wx.ALL, 5 )


        wx.EVT_LIST_ITEM_SELECTED( self.panel_history, id_list, self.OnListItemSelected)
        wx.EVT_LIST_ITEM_DESELECTED( self.panel_history, id_list, self.OnListItemDeselected)
        wx.EVT_LIST_ITEM_SELECTED( self.panel_history, id_paths, self.OnPathItemSelected)
        wx.EVT_LIST_ITEM_DESELECTED( self.panel_history, id_paths, self.OnPathItemDeselected)

        self.initList()

        wx.EVT_SIZE( self.panel_history, self.OnSizeHistory )
        wx.EVT_SIZE( self.panel_comment, self.OnSizeComment )
        wx.EVT_SIZE( self.panel_changed_paths, self.OnSizeChangedPaths )

        for panel, sizer in [
            (self.panel_history, self.v_sizer_history),
            (self.panel_comment, self.v_sizer_comment),
            (self.panel_changed_paths, self.v_sizer_changed_paths)]:

                panel.SetAutoLayout( True )
                panel.SetSizer( sizer )
                sizer.Fit( panel )
                panel.Layout()

    def cmpLogEntries( self, a, b ):
        return cmp( a.rev_number, b.rev_number )

    def initButtons( self, sizer ):
        self.h_sizer = wx.BoxSizer( wx.HORIZONTAL )

        #self.button_view = wx.Button( self.panel_history, id_view_cmd, " View " )
        #self.button_view.Enable( False )

        self.button_diff = wx.Button( self.panel_history, id_diff_cmd, " Diff " )
        self.button_diff.Enable( False )

        # leave View off screen until its implemented
        #self.h_sizer.Add( self.button_view, 0, wx.EXPAND|wx.LEFT, 5 )
        self.h_sizer.Add( self.button_diff, 0, wx.EXPAND|wx.LEFT, 5 )

        sizer.Add( self.h_sizer, 0, wx.EXPAND|wx.ALL, 5 )

        wx.EVT_BUTTON( self.panel_history, id_view_cmd, self.OnViewCommand )
        wx.EVT_BUTTON( self.panel_history, id_diff_cmd, self.OnDiffCommand )

    #---------- Event handlers ----------------------------------------------------------
    def OnListItemSelected( self, event):
        self.updateComment( event.m_itemIndex )
        self.selected_revisions[ event.m_itemIndex ] = None
        self.button_diff.Enable( len( self.selected_revisions ) in [1] )
        self.button_diff.Enable( len( self.selected_revisions ) in [1,2] )

    def OnListItemDeselected( self, event):
        self.updateComment( -1 )
        if event.m_itemIndex in self.selected_revisions:
            del self.selected_revisions[ event.m_itemIndex ]

        self.button_diff.Enable( len( self.selected_revisions ) in [1] )
        self.button_diff.Enable( len( self.selected_revisions ) in [1,2] )

    def OnPathItemSelected( self, event):
        pass

    def OnPathItemDeselected( self, event):
        pass

    #---------- Comment handlers ------------------------------------------------------------
    def updateComment( self, index ):
        if index >= 0:
            message = self.log_entries[ index ].message
            all_paths_info = self.log_entries[ index ].changed_paths
        else:
            message = ''
            all_paths_info = []

        self.comment_ctrl.SetValue( message )
        self.comment_ctrl.SetInsertionPoint( 0 )

        self.log_entries.sort( self.by_rev )

        self.paths_ctrl.DeleteAllItems()
        all_paths_info.sort( self.by_changed_path )
        for index, info in enumerate( all_paths_info ):
            self.paths_ctrl.InsertStringItem( index,
                action_map.get( info.action, info.action ) )
            self.paths_ctrl.SetStringItem( index, self.col_path,
                info.path )
            if info.copyfrom_path is not None:
                self.paths_ctrl.SetStringItem( index, self.col_copyfrom_revision,
                    str( info.copyfrom_revision.number ) )
                self.paths_ctrl.SetStringItem( index, self.col_copyfrom_path,
                    info.copyfrom_path )
            else:
                self.paths_ctrl.SetStringItem( index, self.col_copyfrom_revision, '' )
                self.paths_ctrl.SetStringItem( index, self.col_copyfrom_path, '' )

    def OnSizeHistory( self, event):
        w,h = self.panel_history.GetClientSizeTuple()
        self.v_sizer_history.SetDimension( 0, 0, w, h )

    def OnSizeComment( self, event):
        w,h = self.panel_comment.GetClientSizeTuple()
        self.v_sizer_comment.SetDimension( 0, 0, w, h )

    def OnSizeChangedPaths( self, event):
        w,h = self.panel_changed_paths.GetClientSizeTuple()
        self.v_sizer_changed_paths.SetDimension( 0, 0, w, h )

    def by_changed_path( self, a, b ):
        return cmp( a.path, b.path )

    def by_rev( self, a, b ):
        # highest rev first
        return -cmp( a.rev_number, b.rev_number )

    def initList( self ):
        self.log_entries.sort( self.by_rev )

        for index, log_entry in enumerate( self.log_entries ):
            self.list_ctrl.InsertStringItem( index, str(log_entry.rev_number) )
            self.list_ctrl.SetStringItem( index, self.col_author, log_entry.author )
            self.list_ctrl.SetStringItem( index, self.col_date, wb_subversion_utils.fmtDateTime( log_entry.date ) )
            self.list_ctrl.SetStringItem( index, self.col_label, log_entry.label )
            self.list_ctrl.SetStringItem( index, self.col_message, log_entry.message.replace( '\n', ' ' ) )

    #---------- Command handlers ----------------------------------------------------------
    def OnViewCommand( self, event ):
        print 'Log history View not implemented'

    def OnDiffCommand( self, event ):
        indices = self.selected_revisions.keys()
        indices.sort()
        indices.reverse()

        if len( indices ) not in (1,2):
            return


        info1 = wb_subversion_diff.PathInfoForDiff()

        info1.path = self.log_entries[ indices[0] ].url
        info1.revision = pysvn.Revision( pysvn.opt_revision_kind.number, self.log_entries[ indices[0] ].rev_number )
        info1.peg_path = self.log_entries[ 0 ].url
        info1.peg_revision = pysvn.Revision( pysvn.opt_revision_kind.number, self.log_entries[ 0 ].rev_number )
        info1.title = '%s@%d' % (info1.path, info1.revision.number)

        info2 = info1.copy()

        if len( indices ) == 1:
            info2.path = self.filename
            info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
            info2.peg_path = info2.path
            info2.peg_revision = info2.revision
            info2.title = self.filename
        else:
            info2.path = self.log_entries[ indices[1] ].url
            info2.revision = pysvn.Revision( pysvn.opt_revision_kind.number, self.log_entries[ indices[1] ].rev_number )
            info2.title = '%s@%d' % (info2.path, info2.revision.number)

        generator = self.parent.diffFunction(
                    self.app,
                    self.project_info,
                    info1, info2 )

        #
        #   history does not need to have the diff code run in the background
        #   so just step the generator
        #
        if type(generator) == types.GeneratorType:
            while True:
                try:
                    generator.next()
                except StopIteration:
                    # no problem all done
                    break
