Attribute VB_Name = "MainImpl"
' Main logic implementation
Option Private Module
Option Explicit

Public Function ValidateSelectionRange(target As Word.Document) As Word.Range
  Dim iSelected As Word.Range: Set iSelected = target.Application.Selection.Range
  If iSelected.StoryType <> wdMainTextStory Then
    Call UserInteraction.ShowMessage(EM_SELECTION_NOT_IN_MAIN)
    Exit Function
  End If
  Set ValidateSelectionRange = iSelected
End Function

Public Function EnsureNoLinksSelected(target As Word.Document) As Word.Range
  Dim iSelected As Word.Range: Set iSelected = ValidateSelectionRange(target)
  If iSelected Is Nothing Then
    Exit Function
  ElseIf iSelected.Hyperlinks.Count <> 0 Then
    Call UserInteraction.ShowMessage(EM_LINKS_NOT_ZERO)
    Exit Function
  ElseIf iSelected.Start = iSelected.End Then
    Call UserInteraction.ShowMessage(EM_SELECTION_INVALID)
    Exit Function
  ElseIf iSelected.Paragraphs.Count <> 1 Then
    Call UserInteraction.ShowMessage(EM_PARAGRAPH_MULTIPLE)
    Exit Function
  End If
  Set EnsureNoLinksSelected = iSelected
End Function

Public Function PrepareNewLaw(iDoc As InfoDocument, theDB As InfoWordDatabase) As ItemLaw
  Dim iLaw As ItemLaw: Set iLaw = iDoc.GetLawDescription
  iLaw.isDummy_ = False
  iLaw.status_ = T_LAWS_FULL
  
  If Not theDB.Contains(iDoc.gid_) Then _
    GoTo RETURN_TRUE
  
  Dim docIter As IteratorLaw: Set docIter = theDB.source_.Data(iDoc.gid_)
  Dim sFile$: sFile = docIter.FileName
  If sFile = HT03_FILE_NOT_LOADED Then _
    GoTo RETURN_TRUE
    
  If sFile = iDoc.doc_.Name And theDB.source_.docsLocation_ = iDoc.doc_.Path Then
    Call UserInteraction.ShowMessage(EM_FILE_ALREADY_ADDED)
    Exit Function
  End If
  
  If docIter.IsDummy Then
    If Not UserInteraction.AskQuestion(QM_DELETE_BEFORE_ADD_DUMMY, iDoc.gid_) Then _
      Exit Function
  Else
    If Not UserInteraction.AskQuestion(QM_DELETE_BEFORE_ADD, iDoc.gid_) Then _
      Exit Function
  End If
  
  iLaw.type_ = docIter.DType
  If docIter.Status <> T_LAWS_OUTER Then _
    iLaw.status_ = docIter.Status
  
  Call theDB.source_.DeleteLaw(iDoc.gid_)
  
RETURN_TRUE:
  iLaw.fileName_ = theDB.source_.GenerateFileName(iLaw.title_)
  Set PrepareNewLaw = iLaw
End Function

Public Function AddNewLaw(iDoc As InfoDocument, theDB As InfoWordDatabase, theProps As ItemDocumentProperties)
  iDoc.linkSystem_ = theProps.linkSystem_
  If iDoc.gid_ = HT03_INVALID_GID Then
    iDoc.gid_ = theProps.data_.gid_
    If Not UserInteraction.AskQuestion(QM_ADD_TITLELINK, iDoc.gid_) Then _
      Exit Function
    Call iDoc.CreateTitleLink
  End If
  
  Call iDoc.UpdateStyles(ThisDocument.FullName)
  Call ProcessLawText(iDoc, theDB, theProps)
  Call EnsureDocSaved(iDoc.doc_, theProps.data_.fileName_, theProps.killSource_)
End Function

Public Function UpdateLinksIn(iDoc As InfoDocument, theDB As InfoWordDatabase)
  Dim iLaw As ItemCachedLaw: Set iLaw = theDB.source_.Cache(iDoc.gid_)
  If iLaw.status_ = T_LAWS_OUTER Then
    Call UserInteraction.ShowMessage(IM_SCAN_MODE)
    Exit Function
  End If
  If iLaw.status_ = T_LAWS_IGNORE Then
    Call UserInteraction.ShowMessage(IM_IGNORE_MODE)
    Exit Function
  End If
  If Not theDB.source_.HasLinksFor(iDoc.gid_) Then
    If UserInteraction.AskQuestion(QM_RELOAD_ON_UPDATE, iDoc.gid_) Then
      Call ReloadLinks(iDoc, theDB)
    Else
      Exit Function
    End If
  End If

  Call CSE_ProgressBar.Init("Обновление документа", sHeader:="Форматирование ссылок...", maxVal:=iDoc.doc_.Hyperlinks.Count)
  Call CSE_ProgressBar.ShowModeless
  
  Dim nUnresolved&: nUnresolved = iDoc.UpdateFormat(theDB.source_)
  
  Dim sTime$: sTime = CSE_ProgressBar.TimeStr
  Call Unload(CSE_ProgressBar)

  If nUnresolved <> 0 Then
    Call UserInteraction.ShowMessage(IM_LINKS_TODO, nUnresolved, sTime)
  Else
    Call UserInteraction.ShowMessage(IM_LINKS_DONE, sTime)
  End If
End Function

Public Function ModifyLinkType(target As Word.Range, iDoc As InfoDocument, theDB As InfoWordDatabase) As Boolean
  ModifyLinkType = False
  
  Dim firstLink As Word.Hyperlink: Set firstLink = GetFirstValidHyperlinkIn(target)
  If firstLink Is Nothing Then _
    Exit Function
    
  Dim itLink As IteratorLinkCached: Set itLink = theDB.SyncLink(firstLink, iDoc)
  Dim targetID&: targetID = itLink.DestinationID
  Dim iMod As ItemLinkModification: Set iMod = PromptLinkType(itLink.LType, targetID, firstLink.Address)
  If iMod Is Nothing Then
    ModifyLinkType = True
    Exit Function
  End If
  
  If Not theDB.Contains(targetID) Then _
    If UserInteraction.AskQuestion(QM_OPEN_GARANT, targetID) Then _
      Call UserInteraction.FollowHyperlink(iDoc.doc_, firstLink.Address)
  
  Set iMod.seed_ = target
  Set iMod.sourceLink_ = firstLink
  Set iMod.iterator_ = itLink

  Call ApplyLinkTypeModification(iMod, iDoc, theDB.source_)
  ModifyLinkType = True
End Function

Public Function ToggleLinksStatusIn(target As Word.Range, iDoc As InfoDocument, theDB As InfoWordDatabase)
  Dim itLink As IteratorLinkCached
  Dim docHLink As Word.Hyperlink
  For Each docHLink In target.Hyperlinks
    If IsHyperlinkValid(docHLink) Then
      Set itLink = theDB.SyncLink(docHLink, iDoc)
      Call itLink.FlipStatus
      Call iDoc.ApplyLinkStatus(docHLink, itLink.Status)
    End If
  Next docHLink
End Function

Public Function IgnoreLawsIn(target As Word.Range, iDoc As InfoDocument, theDB As InfoDatabase) As Long
  IgnoreLawsIn = 0
  If target.Hyperlinks.Count = 0 Then _
    Exit Function
  
  Dim iLink As ItemHyperlink
  Dim docHLink As Word.Hyperlink
  Dim docsToIgnore As New Scripting.Dictionary
  For Each docHLink In target.Hyperlinks
    Set iLink = ParseHyperlink(docHLink)
    If iLink Is Nothing Then _
      GoTo NEXT_LINK
    If Not theDB.IgnoreLaw(iLink.DocumentID) Then _
      GoTo NEXT_LINK
    
    Call docsToIgnore.Add(iLink.DocumentID, 0)
    IgnoreLawsIn = IgnoreLawsIn + 1
NEXT_LINK:
  Next docHLink
  
  Call iDoc.IgnoreLawLinks(docsToIgnore)
End Function

Public Function ProcessLawText(target As InfoDocument, theDB As InfoWordDatabase, props As ItemDocumentProperties)
  Dim docUI As New API_WordWrapper: Call docUI.SetDocument(target.doc_)
  Call docUI.PauseUI
  
  Dim newItem As ItemCachedLaw: Set newItem = theDB.source_.SetItem(props.data_)
  If newItem.HasLinks And Not theDB.source_.HasLinksFor(target.gid_) Then
    Call CSE_ProgressBar.Init("Добавление документа", sHeader:="Предварительная обработка...")
    Call CSE_ProgressBar.ShowModeless
    
    Call PrepareDocumentText(target.doc_, props.mods_)
    Call ReloadLinks(target, theDB)
  End If

  On Error Resume Next
  Call UpdateLinksIn(target, theDB)
  Call ProcessErrorMessages(UniqueList(ERR_SYNC_DOC, ERR_SYNC_DB))
  On Error GoTo 0
  
  Call docUI.ResumeUI
End Function

Public Function RemoveLinksIn(target As Word.Range, iDoc As InfoDocument, theDB As InfoDatabase) As Long
  Dim nStart&: nStart = iDoc.CountLinksBefore(target.Start) + 1
  If nStart < 1 Then _
    Exit Function
  Dim itLink As IteratorLinkCached: Set itLink = theDB.LawLinksCached(iDoc.gid_)
  If itLink Is Nothing Then _
    Exit Function
  
  If Not itLink.Increment(nStart - 1) Then _
    Call Err.Raise(ERR_SYNC_DB)
  
  Dim nCount&: nCount = 0
  
  Dim bNoMoreLinks As Boolean: bNoMoreLinks = False
  Dim docHLink As Word.Hyperlink
  For Each docHLink In target.Hyperlinks
    Dim iLink As ItemHyperlink: Set iLink = ParseHyperlink(docHLink)
    If iLink Is Nothing Then _
      GoTo NEXT_LINK
    If bNoMoreLinks Then _
      Call Err.Raise(ERR_SYNC_DB)
    Call iLink.ValidateSync(itLink)
    
    nCount = nCount + 1
    bNoMoreLinks = Not itLink.Increment
NEXT_LINK:
  Next docHLink

  If nCount = 0 Then _
    Exit Function
  
  RemoveLinksIn = nCount
  Call theDB.DeleteSomeLinksFor(iDoc.gid_, nStart, nCount)
  Call iDoc.RemoveLinksFrom(target)
End Function

' Requires: target range should have no hyperlinks
Public Function AddLinkTo(target As Word.Range, iLink As ItemLink, iDoc As InfoDocument, theDB As InfoWordDatabase)
  Debug.Assert target.Hyperlinks.Count = 0
  
  If iLink.destID_ = iLink.sourceID_ Then _
    Call Err.Raise(EM_LINK_SELF)
  If iLink.destSub_ = vbNullString Then _
    iLink.destSub_ = "sub_0"
  If iLink.sourceSub_ = vbNullString Then _
    iLink.sourceSub_ = CreateBookmarkFor(target.Paragraphs(1).Range)
  
  Dim sStyle$: sStyle = GetLinkStatusStyle(theDB.source_.Cache(iLink.sourceID_).status_, theDB.source_.GetLawStatus(iLink.destID_), iLink.type_)
  If Not iDoc.AddHyperlink(target, iLink, sStyle) Then _
    Call Err.Raise(EM_ADD_LINK_DOC_FAILED)
  
  Dim nLink&: nLink = iDoc.CountLinksBefore(target.Start)
  If Not theDB.AddLinkAfter(iLink, nLink) Then
    target.Hyperlinks(1).Range.Style = wdStyleDefaultParagraphFont
    Call target.Hyperlinks(1).Delete
    Call Err.Raise(EM_ADD_LINK_DB_FAILED)
  End If
End Function

Public Function PromptLawProperties(iLaw As ItemLaw, nSystem As TLinkSystem, bEdit As Boolean) As ItemDocumentProperties
  With NewDocumentDlg
    Call .Init(iLaw, nSystem, bEdit)
    Call .Show
    If Not .isCancelled_ Then _
      Set PromptLawProperties = .Properties
  End With
  Call Unload(NewDocumentDlg)
End Function

Public Function PromptLinkType(oldType As TLinkType, gid&, sAddress$) As ItemLinkModification
  Dim iMod As New ItemLinkModification
  With LinkTypeDlg
    Call .Init(oldType)
    Call .Show
    
    If .isCancelled_ Then _
      Exit Function
    If .NeedOpenFile Then
      Call UserInteraction.FollowHyperlink(ActiveDocument, sAddress)
      Exit Function
    End If
    
    iMod.applyToDocument_ = .ApplyToAll
    iMod.spreadToSelected_ = .ApplyToSelected
    iMod.newType_ = .LType
  End With
  Call Unload(LinkTypeDlg)
  
  Set PromptLinkType = iMod
End Function

Public Function PromptNewLink(rAnchor As Word.Range, iDoc As InfoDocument, theDB As InfoDatabase) As ItemLink
  Call NewLinkDlg.Init(iDoc, theDB, rAnchor.Bookmarks)
  Call NewLinkDlg.Show
  If NewLinkDlg.isCancelled_ Then _
    Exit Function
    
  Set PromptNewLink = NewLinkDlg.GetLink
  Call Unload(NewLinkDlg)
End Function

Public Function PromptLinkToNewDoc(rAnchor As Word.Range, iDoc As InfoDocument, theDB As InfoDatabase) As ItemLink
  Call LinkToNewDocDlg.Init(iDoc, theDB, rAnchor.Bookmarks, rAnchor.Text)
  Call LinkToNewDocDlg.Show
  If LinkToNewDocDlg.isCancelled_ Then _
    Exit Function
    
  Dim iNewDoc As ItemLaw: Set iNewDoc = LinkToNewDocDlg.GetDocument
  Call theDB.SetItem(iNewDoc)
  Set PromptLinkToNewDoc = LinkToNewDocDlg.GetLink
  Call Unload(LinkToNewDocDlg)
End Function

Public Function ReloadLinks(target As InfoDocument, theDB As InfoWordDatabase)
  Call CSE_ProgressBar.Init(CSE_ProgressBar.Title, sHeader:="Выгрузка ссылок...", maxVal:=target.doc_.Hyperlinks.Count, curVal:=1)
  Call theDB.LoadLinksFrom(target)
  Call Unload(CSE_ProgressBar)
End Function

Public Function ExecuteLinksSubstitution(iDoc As Word.Document, nDelete&, nSubstitute&, sTitle$)
  Dim iLink As ItemHyperlink
  Dim docHLink As Word.Hyperlink
  For Each docHLink In iDoc.Hyperlinks
    Set iLink = ParseHyperlink(docHLink)
    If iLink Is Nothing Then _
      GoTo NEXT_LINK
    If iLink.DocumentID = nDelete Then
      iLink.DocumentID = nSubstitute
      docHLink.Address = iLink.ToAddress
      docHLink.ScreenTip = sTitle
    End If
    
NEXT_LINK:
  Next docHLink
End Function

' ========================
Private Function EnsureDocSaved(target As Word.Document, theFile$, killSource As Boolean)
  Dim dbDir$: dbDir = AccessDatabase.docsLocation_
  Dim newFullName$: newFullName = dbDir & "\" & theFile
  Dim oldFullName$: oldFullName = target.FullName
  If newFullName = oldFullName Then _
    Exit Function
  
  Dim fso As New Scripting.FileSystemObject
  Call EnsureFolderExists(fso.GetParentFolderName(newFullName))
  Dim removeOld As Boolean: removeOld = killSource Or target.Path = dbDir
  With target
    .SpellingChecked = True
    .GrammarChecked = True
    .Range.NoProofing = True
    Call .SaveAs(newFullName, FileFormat:=wdFormatDocumentDefault)
  End With
  If removeOld Then _
    Call Kill(oldFullName)
End Function

Private Function ApplyLinkTypeModification(iMod As ItemLinkModification, iDoc As InfoDocument, theDB As InfoDatabase)
  Dim sSource As TLawStatus: sSource = theDB.GetLawStatus(iDoc.gid_)
  iMod.iterator_.Status = T_LINKS_ADD
  If Not iMod.applyToDocument_ Then
    If Not iMod.spreadToSelected_ Then
      iMod.iterator_.LType = iMod.newType_
      Call iDoc.FormatLink(iMod.sourceLink_, iMod.newType_, T_LINKS_ADD, sSource, theDB.GetLawStatus(iMod.FirstID))
    Else
      Call ModifyLinksTypeIn(iMod.seed_, iMod.iterator_, iMod.newType_, sSource, iDoc, theDB)
    End If
  Else
    Dim targetDocs As Scripting.Dictionary
    If Not iMod.spreadToSelected_ Then
      Set targetDocs = New Scripting.Dictionary
      Call targetDocs.Add(iMod.FirstID, theDB.GetLawStatus(iMod.FirstID))
    Else
      Set targetDocs = ScanLawIDs(iMod.seed_)
      Call theDB.FillLawsStatus(targetDocs)
    End If
    Call ModifyLinksTypeAll(targetDocs, iMod.iterator_, iMod.newType_, sSource, iDoc)
  End If
End Function

Private Function ModifyLinksTypeIn(target As Word.Range, itLink As IteratorLinkCached, nType As TLinkType, sSource As TLawStatus, iDoc As InfoDocument, theDB As InfoDatabase)
  Dim docHLink As Word.Hyperlink
  Dim iLink As ItemHyperlink
  For Each docHLink In target.Hyperlinks
    Set iLink = ParseHyperlink(docHLink)
    If Not iLink Is Nothing Then
      Dim targetID&: targetID = iLink.DocumentID
      itLink.LType = nType
      Call iDoc.FormatLink(docHLink, nType, itLink.Status, sSource, theDB.GetLawStatus(targetID))
      If Not itLink.Increment Then _
        Exit Function
    End If
  Next docHLink
End Function

Private Function ModifyLinksTypeAll(targetDocs As Scripting.Dictionary, itLink As IteratorLinkCached, nType As TLinkType, sSource As TLawStatus, iDoc As InfoDocument)
  Call itLink.GoFirst
  Dim docHLink As Word.Hyperlink
  Dim iLink As ItemHyperlink
  For Each docHLink In iDoc.doc_.Hyperlinks
    Set iLink = ParseHyperlink(docHLink)
    If Not iLink Is Nothing Then
      Dim targetID&: targetID = iLink.DocumentID
      If targetDocs.Exists(targetID) Then
        itLink.LType = nType
        Call iDoc.FormatLink(docHLink, nType, itLink.Status, sSource, targetDocs(targetID))
      End If
      If Not itLink.Increment Then _
        Exit Function
    End If
  Next docHLink
End Function
