' Gambas module file

Property Read Name As String
Property Read Enabled As Boolean
Property Read Output As String
Property Read DoNotPush As Boolean
Property Read CanCreateBranch As Boolean
Property Read HasIdentity As Boolean

Private $hVC As CVersionControl

Private $bEnabled As Boolean
Private $bEnd As Boolean
Private $hEditor As TextEditor
Private $bOutput As Boolean
Private $sOutput As String
Private $dOutput As Date
Private $bIdent As Boolean

Private $bAuth As Boolean
Private $sUser As String
Private $sPassword As String

Private $sLastResult As String

'Public (User) As String
'Public Password As String
'Public UseTerminal As Boolean

Public LANG_ENV As String[] = ["LC_ALL=C.UTF-8", "LANG=C.UTF-8", "LANGUAGE=C.UTF-8"]
Public Const DELIM_CHANGE As String = ("This line and the following will be ignored")
Public Enum ACCEPT_OURS = 1, ACCEPT_THEIRS = 2

Private $bCancel As Boolean

Private $sCurrentBranch As String
Private $cBranchColor As New Collection

Private $aBranchColor As Integer[] = [&HE00000, &HE00070, &HE000E0, &H6C00E0, &H0000E0, &H0060C0, &H00C0C0, &H00C000, &HC0C000, &HE0A400, &HC05C00, &HA00000, &HA00050, &HA000A0, &H008080, &H008000, &H808000, &HA07800, &HA04A00]

Private Const DEFAULT_BRANCH_COLOR As Integer = &H606060
Private $sDefaultBranch As String

Private Sub OnVersionControlChange()

  FMain.OnVersionControlChange()

End

Private Sub GetTextEditor() As String

  Dim aProg As String[]
  Dim sProg As String
  
  If Desktop.Is("kde4") Or If Desktop.Is("kde5") Then
    aProg = ["kwrite"]
  Else If Desktop.Is("xfce") Then
    aProg = ["mousepad"]
  Else If Desktop.Is("lxde") Then
    aProg = ["leafpad"]
  Else If Desktop.Is("mate") Then
    aProg = ["pluma"]
  Else
    aProg = ["gnome-text-editor", "gedit"]
  Endif
  
  aProg.Insert(["kwrite", "kate", "gnome-text-editor", "gedit", "editor", "emacs", "vim"])
  
  For Each sProg In aProg
    If System.Exist(sProg) Then Return sProg
  Next

End

Public Sub Refresh()

  If Not Project.Dir Then Return

  $hVC = CVersionControl
  $bEnabled = False
  
  If Settings["/VersionControl/Enabled", 1] Then
    If CVersionControlSubversion.Check() Then
      $hVC = CVersionControlSubversion
      $bEnabled = True
    Else If CVersionControlGit.Check() Then
      $hVC = CVersionControlGit
      $bEnabled = True
    Endif
  Endif
  
  $sCurrentBranch = ""
  $sDefaultBranch = ""
  $bAuth = False
  CheckPaths
  OnVersionControlChange
  
  If $bEnabled Then
    
    If Not Env["EDITOR"] Then
      Env["EDITOR"] = GetTextEditor()
      If Not Env["EDITOR"] Then
        Message.Warning(("Please set the $EDITOR environment variable with your favorite graphical text editor. Some version control commands need it."))
      Endif
    Endif
    
    Project.UpdateBookmarks
    
  Endif
  
End

Public Sub Disable()
  
  If $hVC And If $hVC Is CVersionControl Then Return
  
  $hVC = CVersionControl
  $bEnabled = False
  OnVersionControlChange
  
End


Public Sub Insert(sText As String)

  If $hEditor Then
    $hEditor.Print(DConv(sText))
  Else
    FProjectVersion.InsertVersionControl(sText)
  Endif

End

Public Sub Shell(sCmd As String, Optional bSilent As Boolean, Optional aEnv As String[], Optional sBefore As String) As String
  
  Dim sResult As String
  
  If Not bSilent Then Insert(sCmd & "\n")
  If sBefore Then sCmd = sBefore & " " & sCmd
  Shell sCmd With aEnv To sResult
  If Process.LastValue Then 
    $sLastResult = sResult
    Insert(sResult)
  Endif
  Return sResult
  
End

Public Sub Exec(aCmd As String[], Optional bSilent As Boolean, Optional aEnv As String[]) As String
  
  Dim sResult As String
  Dim sArg As String
  
  If Not bSilent Then
    For Each sArg In aCmd
      If InStr(sArg, " ") Then sArg = Quote(sArg)
      Insert(sArg & " ")
    Next
    Insert("\n")
  Endif
  Exec aCmd With aEnv To sResult
  If Process.LastValue Then 
    $sLastResult = sResult
    Insert(sResult)
  Endif
  Return sResult
  
End

Public Sub Run(aCmd As String[], Optional bIdent As Boolean, Optional bOutput As Boolean, Optional aEnv As String[]) As Integer

  ' Dim sPassword As String
  Dim hProcess As Process
  Dim sCmd As String
  
  $bEnd = False
  $bIdent = bIdent
  $sOutput = ""
  $bOutput = bOutput
  $dOutput = Now
  $sLastResult = ""
  $bCancel = False
  
  ' If bIdent Then
  '   'aCmd.Add("--non-interactive")
  '   If User Then
  '     aCmd.Add("--username")
  '     aCmd.Add(User)
  '     If Password Then
  '       sPassword = " --password " & Password
  '       aCmd.Add("--password")
  '       aCmd.Add(Password)
  '     Endif
  '   Endif
  ' Endif
  
  hProcess = Exec aCmd With aEnv For Input Output As "Process" 
  
  sCmd = aCmd.Join(" ")
  'If sPassword Then sCmd = Replace(sCmd, sPassword, "********")

  Insert(sCmd & "\n")

  Do
    Wait Next
    
    If $bEnd Then Break
    
    If DateDiff($dOutput, Now, gb.Second) >= 30 Then
      Try Close #hProcess
      hProcess.Kill
      Process_Error(("Command timeout.") & "\n" & ("Please check your network connection.") & "\n")
      $bCancel = True
      Break
    Endif
    
    'Sleep 0.02
  Loop
  
  If $bCancel Then Return -1
  
  If Process.LastValue Then $sLastResult = $sOutput
  
  Return Process.LastValue

End

Public Sub Process_Error(({Error}) As String)
  
  $sOutput &= {Error}
  Insert({Error})
  
End

Public Sub Process_Read()

  Dim sData As String
  Dim iPos As Integer
  Dim sLine As String
  Dim hProcess As Process

  hProcess = Last
  
  sData = Read #hProcess, -4096
  $sOutput &= Replace(sData, "\r", "")
  If $bOutput Then Insert(sData)
  'Insert(sData)
  $dOutput = Now
  
  iPos = RInStr($sOutput, "\n")
  If iPos = 0 Then
    sLine = $sOutput
  Else
    sLine = Mid$($sOutput, iPos + 1)
  Endif
  
  If $bIdent Then
    If Right(RTrim(sLine)) = ":" Then
      If InStr(sLine, "username", 1, gb.IgnoreCase) Then 
        'If $bOutput Then Insert(sLine)
        If Not $bAuth Then
          If GetAuthentication() Then Goto CANCEL
        Endif
        If Not $sUser Then Goto CANCEL
        Print #Last, $sUser
        Insert($sUser & "\n")
        $sOutput = ""
      Else If InStr(sLine, "password", 1, gb.IgnoreCase) Then 
        'If $bOutput Then Insert(sLine)
        If Not $bAuth Then
          If GetAuthentication() Then Goto CANCEL
        Endif
        Print #Last, $sPassword
        Insert("********" & "\n")
        $sOutput = ""
      Else If InStr(sLine, "passphrase", 1, gb.IgnoreCase) Then 
        'If $bOutput Then Insert(sLine)
        If Not $bAuth Then 
          If GetAuthentication(True) Then Goto CANCEL
        Endif
        Print #Last, $sPassword
        Insert("********" & "\n")
        $sOutput = ""
      Endif
    Else If RTrim(sLine) Like "*(yes/no*)?" Then
      Print #Last, "yes"
      Insert("yes\n")
      $sOutput = ""
    Endif
  Endif
  
  Return
  
CANCEL:

  $bCancel = True
  hProcess.Kill

End

Public Sub Process_Kill()

  $bEnd = True

End

Public Sub AddFile(sPath As String, Optional bForce As Boolean)

  $hVC.AddFile(sPath, bForce)
  $hVC.UpdatePath(sPath)

  ' If $bCVS Then
  '   'NB: For CVS file must already exist
  '   Run(["cvs", "add", sPath])
  ' Endif

End

Public Sub AddDir(sPath As String)

  $hVC.AddDir(sPath)

  ' If $bCVS Then
  '   'NB: For CVS, directory must already exist
  '   Run(["cvs", "add", sPath])
  ' Endif

End

' Must be called *after* the file has been deleted

Public Sub RemoveFile(sPath As String, Optional (bForce) As Boolean)

  $hVC.RemoveFile(sPath, bForce)
  ' If $bCVS Then
  '   'NB: For CVS do 'cvs remove file' first then delete file
  '   Run(["cvs", "remove", sPath])
  ' Endif
  CheckPaths

End

' Must be called *after* the directory has been deleted

Public Sub RemoveDir(sPath As String)

  $hVC.RemoveDir(sPath)
  ' If $bCVS Then
  '   'NB: For CVS do 'cvs remove dirname' first then delete dirname
  '   Run(["cvs", "remove", sPath])
  ' Endif
  CheckPaths

End

' Must be called *after* the file has been moved

Public Sub MoveFile(sOld As String, sNew As String)

  $hVC.MoveFile(sOld, sNew)

  ' If $bCVS Then
  '   'NB: make sure the file is added BEFORE
  '   AddFile(sNew)
  '   'NB: make sure the file deleted AFTER
  '   RemoveFile(sOld)
  ' Endif
  CheckPaths

End

Public Sub CheckPaths()

  Project.ResetFlags
  If Not $bEnabled Then Return
  $hVC.CheckPaths()

End

Public Sub UpdatePath(sPath As String, Optional sPath2 As String)
  
  If Not $bEnabled Then Return
  Project.ResetFlags(sPath)
  If sPath2 Then Project.ResetFlags(sPath2)
  $hVC.UpdatePath(sPath, sPath2)
  
End


Public Sub MoveDir(sOld As String, sNew As String) As Boolean

  $hVC.MoveDir(sOld, sNew)
  CheckPaths

End

Public Sub Status(Optional sPath As String) As String

  Return $hVC.Status(sPath)

End

Public Sub Commit(sChange As String, bDoNotPush As Boolean) As Boolean

  Dim bRes As Boolean
  
  Inc Application.Busy
  bRes = $hVC.Commit(sChange, bDoNotPush)
  If Not bRes Then Project.Refresh
  Dec Application.Busy
  
  If bRes Then
    ShowError(("The project could not be committed."))
  Endif
  
  Return bRes

End

Public Sub Update(Optional sOption As String)

  Dim hFile As Object
  Dim sFile As String
  Dim sPath As String
  Dim aRemove As String[]
  Dim iResult As Integer

  If sOption = "*" Then sOption = ""

  Inc Application.Busy
  iResult = $hVC.Update(sOption)
  CheckPaths
  Project.Refresh
  Dec Application.Busy

  If iResult < 0 Then Return ' cancelled
  
  If iResult Then  
    ShowError(("Unable to update project from repository."))
  Else
    Message.Info(("Project has been updated from repository successfully."))
  Endif
  
  For Each sFile In [".project", ".startup", ".component", ".info"]
    sPath = Project.Dir &/ sFile
    If Exist(sPath) And If Project.IsConflict(sPath) Then SolveConflict(sPath, ACCEPT_THEIRS)
  Next
  For Each sFile In [".settings"]
    sPath = Project.Dir &/ sFile
    If Exist(sPath) And If Project.IsConflict(sPath) Then SolveConflict(sPath, ACCEPT_OURS)
  Next

  aRemove = New String[]
  Project.Workspace.Lock
  For Each hFile In Project.Files
    If hFile Is FConflictEditor Then Continue
    sPath = hFile.Path
    If Project.IsConflict(sPath) Then aRemove.Add(sPath)
  Next
  Project.Workspace.Unlock
  
  Project.Reload
  
  If aRemove.Count Then    
    FMain.DoFilter(Project.FILTER_CONFLICT)
  Endif

End

Public Sub GetUpdateOptions() As Collection
  
  Return $hVC.GetUpdateOptions()
  
End

Public Sub Revert(Optional sPath As String) As Boolean

  Dim sErr As String
  
  If sPath Then Return $hVC.Revert(sPath)

  If Message.Warning(("You are going to cancel your changes!"), ("Continue"), ("Cancel")) = 2 Then Return

  Inc Application.Busy
  
  Try $hVC.Revert()
  If Error Then 
    sErr = Error.Text
    FCommandError.Run(sErr, Status())
  Endif
  'Project.Refresh
  Project.Reload
  
  Dec Application.Busy

End

Public Sub CheckoutSVN(sPath As String, sDir As String, hEditor As TextEditor)

  $hEditor = hEditor
  CVersionControlSubversion.Checkout(sPath, sDir)
  $hEditor = Null

End

Public Sub InConflict(sPath As String) As Boolean

  Return $hVC.InConflict(sPath)

End

Public Sub SolveConflict(sPath As String, Optional iAccept As Integer) As Boolean

  If $hVC.SolveConflict(sPath, iAccept) Then Return True
  
  CheckPaths
  If Project.HasConflict Then
    Project.Refresh
  Else
    Project.Reload
  Endif

End

Public Sub GetVersion(sDir As String) As String

  Return $hVC.GetVersion(sDir)

End

' Private Sub RemoveFirstLines(sStr As String, iCount As Integer) As String
' 
'   Dim iPos As Integer
' 
'   While iCount
'     iPos = InStr(sStr, "\n", iPos + 1)
'     If iPos = 0 Then Return
'     Dec iCount
'   Wend
' 
'   Return Mid$(sStr, iPos + 1)
' 
' End

Public Sub Diff(Optional sPath As String, Optional bFull As Boolean, bNoWhiteSpace As Boolean) As String

  If Not sPath Then sPath = Project.Dir
  Return $hVC.Diff(sPath, bFull, bNoWhiteSpace)

End

Public Sub History(bPrint As Boolean, Optional sPath As String, sPath2 As String, bFull As Boolean, sBranch As String) As String

  If Not sPath Then sPath = Project.Dir
  Return $hVC.History(bPrint, sPath, sPath2, bFull, sBranch)

End

Public Sub InitHistory(hEditor As TextEditor)
  
  $hVC.InitHistory(hEditor)
  
End


Public Sub CanControlProject() As Boolean

  Return False

  ' Dim sDir As String
  ' 
  ' If $hVC <> CVersionControl Then Return
  ' 
  ' If $bSvn17 Then
  '   sDir = $sRealDir
  '   Do
  '     sDir = File.Dir(sDir)
  '     If Exist(sDir &/ ".svn") Then Return True
  '     If sDir = "/" Then Return False
  '   Loop
  ' Else
  '   If Exist($sRealDir &/ "../.svn") Then Return True
  ' Endif
  ' 
  ' If Exist($sRealDir &/ "../CVS") Or Exist($sRealDir &/ "../CVSROOT") Then Return True

End

Public Sub ControlProject(sType As String)

  Dim hVC As CVersionControl
  Dim bErr As Boolean
  
  Select Case sType
    
    Case CVersionControlSubversion.Name
      hVC = CVersionControlSubversion
    
    Case CVersionControlGit.Name
      hVC = CVersionControlGit
      
    Default
      Return
    
  End Select

  If Not hVC.CanControlProject() Then Return

  If Message.Question(Subst(("Do you really want to put this project under version control with &1?"), hVC.LongName), ("Yes"), ("No")) = 2 Then Return
  
  Project.CleanUp
  Try Kill Project.Dir &/ ".settings"

  bErr = hVC.ControlProject()

  Project.Config.Save
  
  If Not bErr Then
    Refresh
    Project.Refresh
    FMain.OnProjectChange()
    FMain.ShowWorkspace(FMain.WORKSPACE_VERSION)
  Else
    ShowError(("The project could not be put under version control."))
  Endif

End

Public Sub Info() As Collection

  Return $hVC.Info()
  
End

Public Sub CleanUp(sPath As String)

  $hVC.CleanUp(sPath)
  
End

Private Function Enabled_Read() As Boolean

  Return $bEnabled

End

Private Function Output_Read() As String

  Return $sOutput

End

Public Sub GetCurrentBranch() As String
  
  If Not $sCurrentBranch Then $sCurrentBranch = $hVC.GetCurrentBranch()
  Return $sCurrentBranch
    
End

Public Sub GetBranches(ByRef sCurrent As String, Optional bTags As Boolean) As String[]
  
  Return $hVC.GetBranches(ByRef sCurrent, bTags)
  
End

Private Function Name_Read() As String

  Return $hVC.Name

End

Public Sub GetDefaultJournal() As String
  
  Return $hVC.GetDefaultJournal()
  
End

Public Sub GetDelimChange() As String

  Dim sStr As String

  sStr = String.Left("-------- " & DELIM_CHANGE & " ", 77)
  sStr &= String$(78 - String.Len(sStr), "-")
  Return sStr

End

Private Sub GetAuthentication(Optional bNoUser As Boolean) As Boolean

  Dialog.NoUser = bNoUser
  Dialog.Key = Project.Name &/ "VersionControl"
  If Dialog.AskPassword() Then Return True
  $sUser = Dialog.User
  $sPassword = Dialog.Password
  $bAuth = True

End

Private Function DoNotPush_Read() As Boolean

  Return $hVC.DoNotPush

End

Public Sub RevertNotImportantFiles()

  Dim sFile As String

  If Settings["/VersionControl/RevertNotImportantFiles", 1] Then

    For Each sFile In [".icon.png", ".gitignore", ".app.png"]
      $hVC.Revert(Project.Dir &/ sFile)
    Next
    
    If Project.VersionFile Then $hVC.Revert(Project.Dir &/ ".version")
    
    CheckPaths
    
  Endif

End

Public Sub GetDefaultBranch() As String
  
  If Not $sDefaultBranch Then $sDefaultBranch = $hVC.GetDefaultBranch()
  Return $sDefaultBranch
  
End


Public Sub SetBranch(sBranch As String)

  Dim sCurrent As String

  Inc Application.Busy
  RevertNotImportantFiles()
  $hVC.SetBranch(sBranch)
  $sCurrentBranch = ""
  sCurrent = GetCurrentBranch()
  If sCurrent = sBranch Then Project.Reload
  Dec Application.Busy
  
  If sCurrent <> sBranch Then
    ShowError(Subst(("Unable to switch to branch `&1`."), sBranch))
  Endif
  
  Refresh()
  
End

Public Sub InitControlProjectMenu(hParent As Menu)
  
  Dim hMenu As Menu
  Dim hVC As CVersionControl
  
  hVC = CVersionControlSubversion
  GoSub CHECK_CONTROL
  hVC = CVersionControlGit
  GoSub CHECK_CONTROL
  Return
  
CHECK_CONTROL:

  If hVC.CanControlProject() Then
    hMenu = New Menu(hParent) As "ControlProject"
    hMenu.Text = Subst(("With &1"), hVC.LongName)
    hMenu.Tag = hVC.Name
  Endif
  
  Return
  
End

Public Sub ControlProject_Click()
  
  ControlProject(Last.Tag)
  
End

Public Sub ShowError(sErr As String)
  
  FCommandError.Run(sErr, $sLastResult)
  
End

Public Sub HasRemote() As Boolean
  
  Return $hVC.HasRemote()
  
End

Public Sub GetSolveConflictMessage() As String
  
  Return $hVC.GetSolveConflictMessage()
  
End

Public Sub NeedMove() As Boolean
  
  Return $hVC.NeedMove
  
End

Public Sub GetBranchColor(sBranch As String) As Integer
  
  Dim iColor As Integer
  Dim I As Integer
  Dim iHash As Integer
  'Dim iPos As Integer
  Dim sCar As String
  
  If Not sBranch Then 
  
    iColor = DEFAULT_BRANCH_COLOR
  
  Else
  
    Try iColor = $cBranchColor[sBranch]
    If Error Then
      
      ' static size_t fnv1a_hash(const char* cp)
      ' {
      '     size_t hash = 0x811c9dc5;
      '     while (*cp) {
      '         hash ^= (unsigned char) *cp++;
      '         hash *= 0x01000193;
      '     }
      '     return hash;
      ' }
      
      sBranch = LCase(sBranch)
      
      Select Case sBranch
        
        Case "master"
          iColor = DEFAULT_BRANCH_COLOR
          
        Case Else 
          
          iHash = 5381
          For I = 1 To Len(sBranch)
            sCar = Mid$(sBranch, I, 1)
            If Not IsAlnum(sCar) Then sCar = "-"
            iHash += Asc(sCar)
            iHash = (iHash * 33) Mod $aBranchColor.Count
          Next
          
          iColor = $aBranchColor[iHash]
        
      End Select
      
      $cBranchColor[sBranch] = iColor
      
    Endif
    
  Endif
  
  If Application.DarkTheme Then iColor = Color.Invert(iColor, True)
  Return iColor
  
End

Public Sub GetBranchFromDir(sDir As String) As String

  sDir = CVersionControlGit.FindRoot(sDir)
  If sDir Then Try Return Trim(Split(File.Load(sDir &/ "HEAD"), "/").Last)

End

Public Sub GetRoot() As String
  
  Return $hVC.GetRoot()
  
End

Public Sub CreateBranch(sNewBranch As String) As Boolean
  
  If $hVC.CreateBranch(sNewBranch) Then
    ShowError(("Unable to create a new branch."))
    Return True
  Endif
  
End

Public Sub GetIdentity(ByRef sName As String, ByRef sEmail As String)
  
  $hVC.GetIdentity(ByRef sName, ByRef sEmail)
  
End

Public Sub SetIdentity(sName As String, sEmail As String)
  
  $hVC.SetIdentity(sName, sEmail)
  
End

Private Function CanCreateBranch_Read() As Boolean

  Return $hVC.CanCreateBranch

End

Private Function HasIdentity_Read() As Boolean

  Return $hVC.HasIdentity

End

' Public Sub FillBranchMenu(hParent As Menu, Optional hMenuButton As MenuButton)
'   
'   Dim sCurrent As String
'   Dim aBranches As String[]
'   Dim sBranch As String
'   Dim hMenu As Menu
'   
'   aBranches = GetBranches(ByRef sCurrent)
'   
'   If Not sCurrent Then
'     If hMenuButton Then
'       hMenuButton.Hide
'     Else 
'       hParent.Hide
'     Endif
'   Else
'     If hMenuButton Then
'       hMenuButton.Show
'       hMenuButton.Text = sCurrent
'     Else
'       hParent.Show
'     Endif
'     hParent.Children.Clear
'     
'     For Each sBranch In aBranches
'       hMenu = New Menu(hParent) As "mnuVcBranchSet"
'       hMenu.Text = sBranch
'       hMenu.Tag = sBranch
'       hMenu.Radio = True
'       hMenu.Checked = sBranch = sCurrent
'     Next
'     
'     If VersionControl.CanCreateBranch Then
'       
'       hMenu = New Menu(hParent)
'       
'       hMenu = New Menu(hParent) As "mnuVcBranchCreate"
'       hMenu.Text = Subst(("Create new branch") & "...", sCurrent)
'       hMenu.Picture = Picture["icon:/small/new-tag"]
'       
'     Endif
'     
'   Endif
'   
' End
' 
' Public Sub mnuVcBranchSet_Click()
'   
'   VersionControl.SetBranch(Last.Tag)
'   
' End

Public Sub AddBranch()
  
  If Not VersionControl.CanCreateBranch Then Return
  If FCreateBranch.Run() Then Return
  Refresh()
  
End

Public Sub InitBranchLabel(lblBranchColor As Label, sBranch As String)
  
  lblBranchColor.Font.Grade = -1
  lblBranchColor.Font.Bold = True
  lblBranchColor.Visible = VersionControl.Enabled
  lblBranchColor.Text = sBranch
  lblBranchColor.Background = GetBranchColor(sBranch)
  lblBranchColor.Foreground = Color.TextBackground
  lblBranchColor.BorderRadius = Desktop.Scale * 2
  lblBranchColor.BorderWidth = Desktop.Scale \ 4
  lblBranchColor.BorderColor = Color.Transparent
  lblBranchColor.Border = Border.Plain
  lblBranchColor.Trim = True
  
End

Public Sub FillBranchCombo(cmbBranch As ComboBox)
  
  Dim sCurrent As String
  Dim aBranch As String[]
  
  aBranch = VersionControl.GetBranches(ByRef sCurrent, True)
  Object.Lock(cmbBranch)
  cmbBranch.List = aBranch
  cmbBranch.Text = sCurrent
  Object.Unlock(cmbBranch)
  
End
