'''
 ====================================================================
 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_tree_handler.py

'''
import os
import types

import pysvn

import wx

import wb_tree_panel
import wb_ids
import wb_subversion_utils
import wb_subversion_project_info
import wb_subversion_utils
import wb_subversion_diff
import wb_subversion_info_dialog
import wb_subversion_history
import wb_subversion_checkin
import wb_subversion_report_updates
import wb_subversion_report_lock
import wb_subversion_report_branch_changes
import wb_subversion_properties_dialog
import wb_clipboard
import wb_dialogs

class SubversionProject(wb_tree_panel.TreeProjectItem):
    def __init__( self, app, project_info ):
        wb_tree_panel.TreeProjectItem.__init__( self )
        self.project_info = project_info
        self.app = app

    def updateStatus( self ):
        self.project_info.updateStatus()

    def getProjectInfo( self ):
        return self.project_info

    def mayExpand( self ):
        dir_status = self.project_info.getDirStatus()
        if dir_status is None:
            # no status available - make a guess
            if not os.path.exists( self.project_info.wc_path ):
                # nothing there 
                return False
            else:
                # some dir assume can expand
                return True

        for file in self.project_info.getTreeFilesStatus():
            if( (file.entry is not None and file.entry.kind == pysvn.node_kind.dir)
            or (file.entry is None and os.path.isdir( file.path )) ):
                return True

        return False

    def getExpansion( self ):
        project_info_list = []

        for file in self.project_info.getTreeFilesStatus():

            if( (file.entry is None and os.path.isdir( file.path ))
            or (file.entry is not None and file.entry.kind == pysvn.node_kind.dir) ):
                pi = wb_subversion_project_info.ProjectInfo( self.app, self.project_info )
                name = os.path.basename( file.path )
                if file.entry is None:
                    # fake up the url - maybe a failed checkout/update
                    url = '%s/%s' % (self.project_info.url, name )
                else:
                    url = file.entry.url

                pi.init( name, url=url, wc_path=file.path,
                    client_fg=self.project_info.client_fg,
                    client_bg=self.project_info.client_bg )
                project_info_list.append( pi )

        return project_info_list

    def getTreeNodeColour( self ):
        dir_status = self.project_info.getDirStatus()
        if dir_status is None:
            # no status available - make a guess
            if not os.path.exists( self.project_info.wc_path ):
                # nothing there
                return wx.RED
            elif not os.path.exists( os.path.join( self.project_info.wc_path, '.svn' ) ):
                # not versioned
                return wx.GREEN
            else:
                # versioned and present
                return wx.BLACK

        elif not os.path.exists( dir_status.path ):
            # nothing there
            return wx.RED
        elif dir_status.text_status in [pysvn.wc_status_kind.unversioned]:
            # unversioned
            return wx.GREEN

        # versioned and present
        return wx.BLACK
    
    def getState( self ):
        state = wb_tree_panel.TreeState()

        dir_status = self.project_info.getDirStatus()


        state.is_project_parent = self.is_project_parent

        if dir_status is None:
            state.modified = False
            state.new_versioned = False
            state.versioned = True
            state.unversioned = False
            state.need_checkin = False
            state.need_checkout = True
            state.conflict = False
            state.file_exists = False
        else:
            state.modified = True
            state.new_versioned = True
            state.versioned = True
            state.unversioned = True
            state.need_checkin = True
            state.need_checkout = False
            state.conflict = True
            state.file_exists = True

            filename = dir_status.path
            if not os.path.exists( filename ):
                state.file_exists = False

            text_status = dir_status.text_status
            if text_status in [pysvn.wc_status_kind.unversioned]:
                state.versioned = False
            else:
                state.unversioned = False

            state.new_versioned = state.new_versioned and text_status in [pysvn.wc_status_kind.added]

            prop_status = dir_status.prop_status
            state.modified = (text_status in [pysvn.wc_status_kind.modified,
                                pysvn.wc_status_kind.conflicted]
                        or
                        prop_status in [pysvn.wc_status_kind.modified,
                                pysvn.wc_status_kind.conflicted])
            state.need_checkin = (text_status in [pysvn.wc_status_kind.added,
                                    pysvn.wc_status_kind.deleted,
                                    pysvn.wc_status_kind.modified]
                            or
                            prop_status in [pysvn.wc_status_kind.added,
                                    pysvn.wc_status_kind.deleted,
                                    pysvn.wc_status_kind.modified])
            state.conflict = text_status in [pysvn.wc_status_kind.conflicted]

        return state

    def getContextMenu( self, state ):
        menu_item =[('', wb_ids.id_Command_Shell, '&Command Shell')
            ,('', wb_ids.id_File_Browser, '&File Browser')
            ,('-', 0, 0 )
            ,('', wb_ids.id_SP_DiffWorkBase, 'Diff WC vs. BASE...' )
            ,('', wb_ids.id_SP_DiffWorkHead, 'Diff WC vs. HEAD...' )
            ,('-', 0, 0 )
            ,('', wb_ids.id_SP_History, 'Log history...' )
            ,('', wb_ids.id_SP_Info, 'Information...' )
            ,('', wb_ids.id_SP_Properties, 'Properties...' )
            ,('-', 0, 0 )]
        if not state.file_exists and state.versioned and self.isProjectParent():
            menu_item += [('', wb_ids.id_SP_Checkout, 'Checkout' )]
        else:
            menu_item += [('', wb_ids.id_SP_Update, 'Update' )]
        menu_item += [('-', 0, '' )
            ,('', wb_ids.id_SP_Checkin, 'Checkin...' )
            ,('-', 0, 0 )
            ,('', wb_ids.id_SP_NewFile, 'New File...' )
            ,('', wb_ids.id_SP_Mkdir, 'Make directory...' )
            ,('', wb_ids.id_SP_Add, 'Add...' )
            ,('', wb_ids.id_SP_Rename, 'Rename...' )
            ,('-', 0, 0 )
            ,('', wb_ids.id_SP_Delete, 'Delete...' )
            ,('', wb_ids.id_SP_Revert, 'Revert...' )
            ,('-', 0, 0 )
            ,('', wb_ids.id_SP_Cleanup, 'Clean up' )
            ]

        return wb_subversion_utils.populateMenu( wx.Menu(), menu_item )

    def Cmd_Dir_EditCopy( self ):
        self.app.setPasteData( wb_clipboard.Clipboard( [self.project_info.wc_path], is_copy=True ) )
        print 'Copied folder %s to the Clipboard' % self.project_info.wc_path
        self.app.refreshFrame()
 
    def Cmd_Dir_EditCut( self ):
        self.app.setPasteData( wb_clipboard.Clipboard( [self.project_info.wc_path], is_copy=False ) )
        print 'Cut folder %s to the Clipboard' % self.project_info.wc_path
        self.app.refreshFrame()

    def Cmd_Dir_EditPaste( self ):
        return self.app.frame.list_panel.OnSpEditPaste()

    def Cmd_Dir_Add( self ):
        name = os.path.basename( self.project_info.wc_path )
        force = self.app.addFile( 'Add Directory', name, False )
        if force is None:
            return

        try:
            self.project_info.client_fg.add( self.project_info.wc_path, recurse=True, force=force )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        self.app.refreshFrame()

    def Cmd_Dir_Checkout( self ):
        self.app.setAction( 'Checkout %s...' % self.project_info.url )

        yield self.app.backgroundProcess
        try:
            # lose any trailing / on the URL it stops checkout working
            url = self.project_info.url
            if url[-1] == '/':
                url = url[:-1]
            self.project_info.client_bg.checkout( url, self.project_info.wc_path, recurse=True )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

    def Cmd_Dir_Cleanup( self ):
        self.app.setAction( 'Clean up %s...' % self.project_info.wc_path )

        yield self.app.backgroundProcess
        try:
            self.project_info.client_bg.cleanup( self.project_info.wc_path )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

    def Cmd_Dir_Checkin( self ):
        # get a status output to show the user

        self.app.setAction( 'Look for changes to check in %s...' % self.project_info.wc_path )

        error = False
        yield self.app.backgroundProcess
        try:
            all_files = self.project_info.client_bg.status(
                self.project_info.wc_path,
                recurse=True, get_all=False,
                ignore=True, update=False )
            all_files = [entry for entry in all_files
                            if wb_subversion_utils.wc_status_checkin_map[ entry.text_status ]
                            or wb_subversion_utils.wc_status_checkin_map[ entry.prop_status ]]
        except pysvn.ClientError, e:
            self.app.log_client_error( e )
            error = True
        yield self.app.foregroundProcess

        self.app.setAction( 'Ready' )

        if error:
            return

        if len(all_files) == 0:
            wx.MessageBox( "There are no changes to check in",
                "Warning", style=wx.OK|wx.ICON_EXCLAMATION )
            return

        ci_frame = wb_subversion_checkin.CheckinFrame( self.app, self.project_info, all_files )
        ci_frame.Show( True )

    def __cmd_status_output( self, all_files ):
        output_lines = []
        all_files.sort( wb_subversion_utils.by_path )
        for file in all_files:
            state = wb_subversion_utils._status_format( file )

            if( wb_subversion_utils.wc_status_checkin_map[ file.text_status ]
            or  wb_subversion_utils.wc_status_checkin_map[ file.prop_status ] ):
                output_lines.append( (state, file.path) )

        return output_lines

    def Cmd_Dir_Delete( self ):
        if self.app.confirmAction( 'Delete', [('', self.project_info.wc_path)] ):
            try:
                self.project_info.client_fg.remove( self.project_info.wc_path )
            except pysvn.ClientError, e:
                self.app.log_client_error( e )
            self.app.refreshFrame()

    def Cmd_Dir_DiffWorkBase( self ):
        filename = self.project_info.wc_path

        self.app.setAction( 'Diff BASE %s...' % filename )

        info1 = wb_subversion_diff.PathInfoForDiff()

        info1.path = filename
        info1.revision = pysvn.Revision( pysvn.opt_revision_kind.base )
        info1.title = filename + '@BASE'

        info2 = wb_subversion_diff.PathInfoForDiff()

        info2.path = filename
        info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
        info2.title = filename

        generator = wb_subversion_diff.subversionDiffDir(
                        self.app,
                        self.project_info,
                        info1,
                        info2 )

        if type(generator) == types.GeneratorType:
            while True:
                try:
                    where_to_go_next = generator.next()
                    
                except StopIteration:
                    # no problem all done
                    break

                yield where_to_go_next

        self.app.setAction( 'Ready' )

    def Cmd_Dir_DiffWorkHead( self ):
        filename = self.project_info.wc_path

        self.app.setAction( 'Diff HEAD %s...' % filename )


        info1 = wb_subversion_diff.PathInfoForDiff()

        info1.path = filename
        info1.revision = pysvn.Revision( pysvn.opt_revision_kind.head )
        info1.title = filename + '@HEAD'

        info2 = wb_subversion_diff.PathInfoForDiff()

        info2.path = filename
        info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
        info2.title = filename

        generator = wb_subversion_diff.subversionDiffDir(
                            self.app,
                            self.project_info,
                            info1,  info2 )

        if type(generator) == types.GeneratorType:
            while True:
                try:
                    where_to_go_next = generator.next()
                except StopIteration:
                    # no problem all done
                    break

                yield where_to_go_next

        self.app.setAction( 'Ready' )


    def Cmd_Dir_History( self ):
        filename = self.project_info.wc_path

        dialog = wb_subversion_history.HistoryDialog( self.app.frame.tree_panel.tree_ctrl )
        result = dialog.ShowModal()
        if result != wx.ID_OK:
            return

        self.app.setAction( 'Log history %s...' % filename )

        yield self.app.backgroundProcess

        ok = False
        history_entries = []
        try:
            file_url, history_entries = wb_subversion_history.getHistoryEntries(
                        self.project_info,
                        filename,
                        dialog.getLimit(),
                        dialog.getRevisionEnd() )
            ok = True
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        if ok:
            h_frame = wb_subversion_history.HistoryDirFrame(
                self.app,
                self.project_info,
                filename,
                file_url,
                history_entries )
            h_frame.Show( True )

        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

    def Cmd_Dir_Info( self ):
        try:
            if hasattr( self.project_info.client_fg, 'info2' ):
                entry = self.project_info.client_fg.info2( filename, recurse=False )
            else:
                entry = self.project_info.client_fg.info( filename )

            dialog = wb_subversion_info_dialog.InfoDialog( self.app,
                    self.app.frame.tree_panel.tree_ctrl,
                    self.project_info.wc_path,
                    entry[0] )
            dialog.ShowModal()

        except pysvn.ClientError, e:
            self.app.log_client_error( e )


    def Cmd_Dir_Mkdir( self ):
        rc, name = self.app.getFilename( 'Make directory', 'Name:' )
        if not rc:
            return

        try:
            new_path = os.path.join( self.project_info.wc_path, name )
            # pass an empty message
            self.project_info.client_fg.mkdir( new_path,  '' )

        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        # open up the current tree node to show the created item
        self.app.expandSelectedTreeNode()

    def Cmd_Dir_NewFile( self ):
        pi = self.app.frame.tree_panel.getProjectTopProjectInfo()

        template_dir = ''
        if pi is not None:
            template_dir = pi.new_file_template_dir

        dialog = wb_dialogs.NewFile( self.app.frame.tree_panel.tree_ctrl, template_dir )
        if dialog.ShowModal() == wx.ID_OK:
            template_contents = ''
            if( template_dir != ''
            and dialog.getTemplateFilename() is not None ):
                try:
                    template_filename = os.path.join( template_dir, dialog.getTemplateFilename() )
                    t = file( template_filename, 'r' )
                    template_contents = t.read()
                    t.close()
                except EnvironmentError, e:
                    self.app.log.error( 'Cannot read template %s - %s' % (template_filename, str(e)) )
                    return

            try:
                new_filename = os.path.join( self.project_info.wc_path, dialog.getNewFilename() )
                f = file( new_filename, 'w' )
                f.write( template_contents )
                f.close()
            except EnvironmentError, e:
                self.app.log.error( 'Cannot create new file %s - %s' % (new_filename, str(e)) )
                return

            try:
                self.project_info.client_fg.add( new_filename )
            except pysvn.ClientError, e:
                self.app.log_client_error( e )

        self.app.refreshFrame()

    def Cmd_Dir_Properties( self ):
        client_fg = self.project_info.client_fg

        filename = self.project_info.wc_path

        try:
            prop_list = client_fg.proplist( filename,
                    revision=pysvn.Revision( pysvn.opt_revision_kind.working ) )
            if len(prop_list) == 0:
                prop_dict = {}
            else:
                _, prop_dict = prop_list[0]
            dialog = wb_subversion_properties_dialog.DirPropertiesDialog( self.app,
                    self.app.frame.tree_panel.tree_ctrl,
                    filename,
                    prop_dict )
            if dialog.ShowModal() == wx.OK:
                for present, name, value in dialog.getModifiedProperties():
                    if not present:
                        # delete name
                        client_fg.propdel( name, filename )
                    else:
                        # add/update name value
                        client_fg.propset( name, value, filename )

        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        self.app.refreshFrame()

    def Cmd_Dir_ReportLocksWc( self ):
        return self.__reportLocksCommon( show_repos_locks=False )

    def Cmd_Dir_ReportLocksRepos( self ):
        return self.__reportLocksCommon( show_repos_locks=True )

    def __reportLocksCommon( self, show_repos_locks ):

        if show_repos_locks:
            self.app.setAction( 'Looking for repository locks in %s...' % self.project_info.wc_path )
        else:
            self.app.setAction( 'Looking for working copy locks in %s...' % self.project_info.wc_path )

        ok = True
        yield self.app.backgroundProcess
        try:
            all_files = self.project_info.client_bg.status(
                self.project_info.wc_path,
                recurse=True, get_all=False,
                ignore=True, update=show_repos_locks )
            all_files = [status for status in all_files
                            if (status.entry is not None and status.entry.lock_token is not None)
                            or (status.repos_lock is not None)]
        except pysvn.ClientError, e:
            self.app.log_client_error( e )
            ok = False
        yield self.app.foregroundProcess

        self.app.setAction( 'Ready' )

        if not ok:
            return

        if len(all_files) == 0:
            if show_repos_locks:
                wx.MessageBox( "There are no locked files in the repository",
                    "Warning", style=wx.OK|wx.ICON_EXCLAMATION )
            else:
                wx.MessageBox( "There are no locked files in the working copy",
                    "Warning", style=wx.OK|wx.ICON_EXCLAMATION )
            return

        ci_frame = wb_subversion_report_lock.ReportLockFrame(
                        self.app, self.project_info, all_files,
                        show_repos_locks=show_repos_locks )
        ci_frame.Show( True )

    def Cmd_Dir_ReportUpdates( self ):
        self.app.setAction( 'Updates %s...' % self.project_info.url )

        ok = False
        all_files = []

        yield self.app.backgroundProcess
        try:
            all_files = self.project_info.client_bg.status(
                self.project_info.wc_path,
                recurse=True, get_all=False,
                ignore=True, update=True )
            ok = True

        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        if len(all_files) == 0:
            wx.MessageBox( "All files are up todate",
                "Warning", style=wx.OK|wx.ICON_EXCLAMATION )
            return

        self.app.setAction( 'Ready' )

        if ok:
            all_files = [status for status in all_files
                            if status.repos_prop_status != pysvn.wc_status_kind.none
                            or (status.entry is not None
                                and status.entry.kind == pysvn.node_kind.file
                                and status.repos_text_status != pysvn.wc_status_kind.none)]
            ci_frame = wb_subversion_report_updates.ReportUpdatesFrame( self.app, self.project_info, all_files )
            ci_frame.Show( True )

    def Cmd_Dir_ReportBranchChanges( self ):
        self.app.setAction( 'Branch changes %s...' % self.project_info.url )

        changed_files = []
        is_branch = False

        yield self.app.backgroundProcess
        
        try:
            log_entries = self.project_info.client_bg.log( self.project_info.url, discover_changed_paths=True )
            if log_entries[-1]['changed_paths'][0]['copyfrom_path'] is not None:
                is_branch = True
                all_files = self.project_info.client_bg.status( self.project_info.wc_path,
                    recurse=True, get_all=True, ignore=True, update=False )
                repos_root_url = self.project_info.client_bg.info2( self.project_info.wc_path )[0][1]['repos_root_URL']
                changed_urls = []
                for log_entry in log_entries:
                    for changed_paths in log_entry['changed_paths']:
                        url = repos_root_url + changed_paths['path']
                        if not (url in changed_urls):
                            changed_urls.append( url )
                for status in all_files:
                    if (status.entry is not None 
                        and status.entry.kind == pysvn.node_kind.file
                        and (status.text_status != pysvn.wc_status_kind.normal or 
                             status.entry.url in changed_urls)):
                        changed_files.append( status )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        if not is_branch:
            wx.MessageBox( '"%s" is not a branch.' % self.project_info.url, "Error", style=wx.OK|wx.ICON_ERROR )
        elif len( changed_files ) == 0:
            wx.MessageBox( 'No files changed yet in branch "%s".' % self.project_info.url, "Error", style=wx.OK|wx.ICON_ERROR )
        else:
            ci_frame = wb_subversion_report_branch_changes.ReportBranchChangesFrame( self.app, self.project_info, changed_files )
            ci_frame.Show( True )

        self.app.setAction( 'Ready' )


    def Cmd_Dir_Rename( self ):
        old_filename = self.project_info.wc_path
        old_name = os.path.basename( old_filename )

        new_name, force = self.app.renameFile( "Rename Directory", old_name, False )

        if new_name is None:
            return

        if new_name != old_name:
            new_full_filename = os.path.join( os.path.dirname( old_filename ), new_name )
            dir_status = self.project_info.getDirStatus()
            print 'Rename',old_filename, new_full_filename
            if dir_status is None or dir_status.text_status not in [pysvn.wc_status_kind.unversioned]:
                try:
                    self.project_info.client_fg.move( old_filename, new_full_filename, force=force )
                except pysvn.ClientError, e:
                    self.app.log_client_error( e )
            else:
                try:
                    os.rename( old_filename, new_full_filename )
                except (OSError,IOError), e:
                    self.app.log.error( str(e) )

            self.app.selectTreeNodeInParent( new_name )
        self.app.refreshFrame()

    def Cmd_Dir_Revert( self ):
        # get a status output to show the user
        all_files = self.project_info.client_fg.status( self.project_info.wc_path, recurse=True, get_all=False, ignore=True, update=False )
        status_output = self.__cmd_status_output( all_files )
        if len(status_output) == 0:
            wx.MessageBox( "There are no changes to revert",
                "Warning", style=wx.OK|wx.ICON_EXCLAMATION )
            return


        if self.app.confirmAction( 'Revert', status_output ):
            try:
                self.project_info.client_fg.revert( self.project_info.wc_path, recurse=True )
            except pysvn.ClientError, e:
                self.app.log_client_error( e )
            self.app.refreshFrame()

    def Cmd_Dir_Update( self ):
        self.app.setAction( 'Update %s...' % self.project_info.wc_path )
        self.app.setProgress( 'Updated %(count)d', 0 )

        yield self.app.backgroundProcess

        filename = self.project_info.wc_path
        ok = False
        try:
            rev_list = self.project_info.client_bg.update( filename, recurse=True )
            ok = True
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        yield self.app.foregroundProcess

        if ok:
            for rev in rev_list:
                if rev.number > 0:
                    count = self.app.getProgressValue( 'count' )
                    if count == 0:
                        self.app.log.info( 'Updated %s to revision %d, no new updates' % (filename, rev.number) )
                    elif count == 1:
                        self.app.log.info( 'Updated %s to revision %d, 1 new update' % (filename, rev.number) )
                    else:
                        self.app.log.info( 'Updated %s to revision %d, %d new updates' % (filename, rev.number, count) )
                else:
                    self.app.log.warning( 'Already up to date' )

        self.app.clearProgress()
        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

    def Cmd_Dir_Copy( self, all_filenames ):
        try:
            for src_filename in all_filenames:
                self.project_info.client_fg.copy( src_filename, self.project_info.wc_path )
                self.app.log.info( 'Copied %s to %s' % (src_filename, self.project_info.wc_path) )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        self.app.refreshFrame()

    def Cmd_Dir_Move( self, all_filenames ):
        try:
            for src_filename in all_filenames:
                self.project_info.client_fg.move( src_filename, self.project_info.wc_path )
                self.app.log.info( 'Moved %s to %s' % (src_filename, self.project_info.wc_path) )
        except pysvn.ClientError, e:
            self.app.log_client_error( e )

        self.app.refreshFrame()
