//
//  OpenInLibrary.swift
//  ElCanari
//
//  Created by Pierre Molinaro on 05/01/2019.
//
//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

import Cocoa

//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

class OpenInLibrary : NSObject, AutoLayoutTableViewDelegate {

  //····················································································································

  private final let mDialog : NSPanel
  private final let mOpenButton : AutoLayoutSheetDefaultOkButton
  private final let mCancelButton : AutoLayoutSheetCancelButton
  private final let mTableView = AutoLayoutTableView (size: .regular, addControlButtons: false)
  private final let mStatusTextField = AutoLayoutStaticLabel (title: "", bold: false, size: .regular).set (alignment: .left).expandableWidth ()
  private final let mFullPathTextField = AutoLayoutStaticLabel (title: "", bold: false, size: .regular).set (alignment: .left).expandableWidth ()
  private final var mPartImage = AutoLayoutImageObserverView (size: .regular)
  private final let mNoSelectedPartTextField = AutoLayoutStaticLabel (title: "", bold: true, size: .regular)
  private final let mNoSelectedPartView = AutoLayoutVerticalStackView ()
  private final let mSearchField = AutoLayoutSearchField (width: 300, size: .regular)

  //····················································································································

  private final var mTableViewDataSource = [OpenInLibraryDialogItem] ()
  private final var mTableViewFilteredDataSource = [OpenInLibraryDialogItem] ()

  //····················································································································

  override init () {
  //--- Dialog
    self.mDialog = NSPanel (
      contentRect: NSRect (x: 0, y: 0, width: 700, height: 600),
      styleMask: [.titled],
      backing: .buffered,
      defer: false
    )
    let mainView = AutoLayoutVerticalStackView ().set (margins: 20)
    let topView = AutoLayoutHorizontalStackView ()
  //--- First column
    let firstColumn = AutoLayoutVerticalStackView ()
    firstColumn.appendView (self.mSearchField)
    firstColumn.appendView (self.mTableView)
    topView.appendView (firstColumn)
 //--- Second Column
    self.mNoSelectedPartView.appendFlexibleSpace ()
    self.mNoSelectedPartView.appendViewSurroundedByFlexibleSpaces (self.mNoSelectedPartTextField)
    self.mNoSelectedPartView.appendFlexibleSpace ()
    topView.appendView (self.mNoSelectedPartView)
  //---
    topView.appendView (self.mPartImage)
  //--- Add view
    mainView.appendView (topView)
  //--- Grid view: status and path
    let gridView = AutoLayoutGridView2 ()
      .addFirstBaseLineAligned (
        left: AutoLayoutStaticLabel (title: "Status:", bold: false, size: .regular).notExpandableWidth (),
        right: self.mStatusTextField
      )
      .addFirstBaseLineAligned (
        left: AutoLayoutStaticLabel (title: "Path:", bold: false, size: .regular),
        right: self.mFullPathTextField
      )
    mainView.appendView (gridView)
  //--- Bottom view
    let bottomView = AutoLayoutHorizontalStackView ()
    self.mCancelButton = AutoLayoutSheetCancelButton (title: "Cancel", size: .regular)
    bottomView.appendView (self.mCancelButton)
    bottomView.appendFlexibleSpace ()
    self.mOpenButton = AutoLayoutSheetDefaultOkButton (title: "Open", size: .regular, sheet: self.mDialog)
    bottomView.appendView (self.mOpenButton)
    mainView.appendView (bottomView)
  //--- Set content view
    self.mDialog.contentView = mainView
  //---
    super.init ()
  //---
    _ = self.mSearchField.bind_run (target: self, selector: #selector (Self.searchFieldAction (_:)))
  //--- Configure table view
    self.mTableView.configure (
      allowsEmptySelection: false,
      allowsMultipleSelection: false,
      rowCountCallBack: { [weak self] in self?.mTableViewFilteredDataSource.count ?? 0 },
      delegate: self
    )
    self.mTableView.addColumn_NSImage (
      valueGetterDelegate: { [weak self] in self?.mTableViewFilteredDataSource [$0].statusImage () },
      valueSetterDelegate: nil,
      sortDelegate: nil,
      title: "Status",
      minWidth: 50,
      maxWidth: 50,
      headerAlignment: .left,
      contentAlignment: .center
    )
    self.mTableView.addColumn_String (
      valueGetterDelegate: { [weak self] in self?.mTableViewFilteredDataSource [$0].mPartName },
      valueSetterDelegate: nil,
      sortDelegate: nil,
      title: "Name",
      minWidth: 250,
      maxWidth: 2_000,
      headerAlignment: .left,
      contentAlignment: .left
    )
  }

  //····················································································································

  private final func configureWith (alreadyLoadedDocuments inNames : Set <String>) {
    self.mNoSelectedPartTextField.stringValue = self.noPartMessage ()
    self.mOpenButton.isEnabled = false
    self.mPartImage.image = nil
    self.mPartImage.isHidden = true
    self.mNoSelectedPartView.isHidden = false
    self.buildDataSource (alreadyLoadedDocuments: inNames)
    self.mTableView.sortAndReloadData ()
  }

  //····················································································································
  //   Load document, displayed as sheet
  //····················································································································

  final func loadDocumentFromLibrary (windowForSheet inWindow : NSWindow,
                                      alreadyLoadedDocuments inNames : Set <String>,
                                      callBack inCallBack : @escaping (_ inData : Data, _ inName : String) -> Bool,
                                      postAction inPostAction : Optional <() -> Void>) {
  //--- Configure
    self.configureWith (alreadyLoadedDocuments: inNames)
    _ = self.mOpenButton.setDismissAction ()
  //--- Dialog
    inWindow.beginSheet (self.mDialog) { (_ inModalResponse : NSApplication.ModalResponse) in
      let selectedRow = self.mTableView.selectedRow
      if inModalResponse == .stop, selectedRow >= 0 {
        let selectedItem = self.mTableViewFilteredDataSource [selectedRow]
        if selectedItem.mFullPath != "" {
          let fm = FileManager ()
          if let data = fm.contents (atPath: selectedItem.mFullPath) {
            let ok = inCallBack (data, selectedItem.mFullPath.lastPathComponent.deletingPathExtension)
            if ok {
              inPostAction? ()
            }
          }
        }
      }
      self.removeAllEntries ()
    }
  }

  //····················································································································
  //   Open document in library, displayed as dialog window
  //····················································································································

  final func openDocumentInLibrary (windowTitle inTitle : String) {
  //--- Configure
    self.mDialog.title = inTitle
    self.configureWith (alreadyLoadedDocuments: [])
    _ = self.mOpenButton.bind_run (target: self, selector: #selector (Self.stopModalAndOpenDocumentAction (_:)))
  //--- Dialog
    _ = NSApp.runModal (for: self.mDialog)
  }

  //····················································································································

  private final func removeAllEntries () {
    self.mTableViewDataSource = []
    self.mTableViewFilteredDataSource = []
    self.mTableView.sortAndReloadData ()
  }

  //····················································································································

  @objc private func stopModalAndOpenDocumentAction (_ inSender : Any?) {
    NSApp.stopModal ()
    self.mDialog.orderOut (nil)
    let selectedRow = self.mTableView.selectedRow
    if selectedRow >= 0 {
      let selectedItem = self.mTableViewFilteredDataSource [selectedRow]
      if selectedItem.mFullPath != "" {
        let dc = NSDocumentController.shared
        let url = URL (fileURLWithPath: selectedItem.mFullPath)
        dc.openDocument (withContentsOf: url, display: true) { (document : NSDocument?, alreadyOpen : Bool, error : Error?) in }
      }
    }
    self.removeAllEntries ()
  }

  //····················································································································

  func buildDataSource (alreadyLoadedDocuments inNames : Set <String>) { // Abstract method
  }

  //····················································································································

  func noPartMessage () -> String { // Abstract method
    return "?"
  }

  //····················································································································

  func partLibraryPathForPath (_ inPath : String) -> String {  // Abstract method
    return inPath
  }

  //····················································································································

  final func buildTableViewDataSource (extension inFileExtension : String,
                                       alreadyLoadedDocuments inNames : Set <String>,
                                       _ inBuildPreviewShapeFunction : @escaping (_ inRootObject : EBManagedObject?) -> NSImage?) {
    do{
      let fm = FileManager ()
    //--- Build part dictionary
      var partCountDictionary = [String : Int] ()
      for path in existingLibraryPathArray () {
        let baseDirectory = self.partLibraryPathForPath (path)
        var isDirectory : ObjCBool = false
        if fm.fileExists (atPath: baseDirectory, isDirectory: &isDirectory), isDirectory.boolValue {
          let files = try fm.subpathsOfDirectory (atPath: baseDirectory)
          for f in files {
            if f.pathExtension == inFileExtension {
              let baseName = f.lastPathComponent.deletingPathExtension
              partCountDictionary [baseName] = partCountDictionary [baseName, default: 0] + 1
            }
          }
        }
      }
    //--- Build data source arraies
      var tableViewDataSource = [OpenInLibraryDialogItem] ()
      for path in existingLibraryPathArray () {
        let baseDirectory = self.partLibraryPathForPath (path)
        var isDirectory : ObjCBool = false
        if fm.fileExists (atPath: baseDirectory, isDirectory: &isDirectory), isDirectory.boolValue {
          let files = try fm.subpathsOfDirectory (atPath: baseDirectory)
          for f in files {
            if f.pathExtension == inFileExtension {
              let fullpath = baseDirectory + "/" + f
              let baseName = f.lastPathComponent.deletingPathExtension
              let isDuplicated : Bool = (partCountDictionary [baseName] ?? 0) > 1
              let pathAsArray = f.deletingPathExtension.components (separatedBy: "/")
              enterPart (&tableViewDataSource, pathAsArray, fullpath, isDuplicated, inNames.contains (baseName), inBuildPreviewShapeFunction)
            }
          }
        }
      }
      self.mTableViewDataSource = tableViewDataSource
      self.searchFieldAction (nil)
    }catch (let error) {
      let alert = NSAlert (error: error)
      _ = alert.runModal ()
    }
  }

  //····················································································································
  //   AutoLayoutTableViewDelegate protocol methods
  //····················································································································

  final func tableViewSelectionDidChange (selectedRows inSelectedRows : IndexSet) {
    let selectedRow = self.mTableView.selectedRow
    if selectedRow >= 0 {
      let selectedPart = self.mTableViewFilteredDataSource [selectedRow]
      self.mStatusTextField.stringValue = selectedPart.statusString ()
      self.mFullPathTextField.stringValue = selectedPart.mFullPath
      self.mOpenButton.isEnabled = true
      self.mPartImage.image = selectedPart.image
      self.mPartImage.isHidden = false
      self.mNoSelectedPartView.isHidden = true
    }else{
      self.mStatusTextField.stringValue = "—"
      self.mFullPathTextField.stringValue = "—"
      self.mOpenButton.isEnabled = false
      self.mPartImage.image = nil
      self.mPartImage.isHidden = true
      self.mNoSelectedPartView.isHidden = false
    }
  }

  //····················································································································

  final func indexesOfSelectedObjects () -> IndexSet {
    return .init ()
  }

  //····················································································································

  final func addEntry () {
  }

  //····················································································································

  final func removeSelectedEntries () {
  }

  //····················································································································

  final func beginSorting () {
  }

  //····················································································································

  final func endSorting () {
  }

  //····················································································································
  //   SEARCH FIELD ACTION
  //····················································································································

  @objc private func searchFieldAction (_ inUnusedSender : Any?) {
    let filter = self.mSearchField.stringValue.uppercased ()
  //--- Table view
    if filter.isEmpty {
      self.mTableViewFilteredDataSource = self.mTableViewDataSource
    }else{
      self.mTableViewFilteredDataSource = []
      for entry in self.mTableViewDataSource {
        if entry.mPartName.uppercased ().contains (filter) {
          self.mTableViewFilteredDataSource.append (entry)
        }
      }
    }
    self.mTableViewFilteredDataSource.sort { $0.mPartName < $1.mPartName }
    self.mTableView.sortAndReloadData ()
  }

  //····················································································································

}

//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

fileprivate func enterPart (_ tableViewDataSource : inout [OpenInLibraryDialogItem],
                            _ inPathAsArray : [String],
                            _ inFullpath : String,
                            _ inIsDuplicated : Bool,
                            _ inIsAlreadyLoaded : Bool,
        _ inBuildPreviewShapeFunction : @escaping (_ inRootObject : EBManagedObject?) -> NSImage?) {
  if inPathAsArray.count == 1 {
    tableViewDataSource.append (OpenInLibraryDialogItem (inPathAsArray [0], inFullpath, inIsDuplicated, inIsAlreadyLoaded, inBuildPreviewShapeFunction))
  }else{
    var pathAsArray = inPathAsArray
    pathAsArray.remove (at: 0)
    enterPart (&tableViewDataSource, pathAsArray, inFullpath, inIsDuplicated, inIsAlreadyLoaded, inBuildPreviewShapeFunction)
  }
}

//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

fileprivate final class OpenInLibraryDialogItem : EBSwiftBaseObject {

  let mPartName : String
  let mIsDuplicated : Bool
  let mIsAlreadyLoaded : Bool
  let mFullPath : String
  private var mPartStatus : MetadataStatus? = nil
  private var mObjectImage : NSImage? = nil
  private var mBuildPreviewShapeFunction : (_ inRootObject : EBManagedObject?) -> NSImage?

  //····················································································································

  init (_ inPartName : String,
        _ inFullPath : String,
        _ inIsDuplicated : Bool,
        _ inIsAlreadyLoaded : Bool,
        _ inBuildPreviewShapeFunction : @escaping (_ inRootObject : EBManagedObject?) -> NSImage?) {
    self.mPartName = inPartName
    self.mFullPath = inFullPath
    self.mIsDuplicated = inIsDuplicated
    self.mIsAlreadyLoaded = inIsAlreadyLoaded
    self.mBuildPreviewShapeFunction = inBuildPreviewShapeFunction
  }

  //····················································································································

  func statusString () -> String {
    if self.mFullPath.isEmpty {
      return ""
    }else if self.mIsAlreadyLoaded {
      return "Already loaded"
    }else if self.mIsDuplicated {
      return "Duplicated"
    }else{
      do{
        switch try self.partStatus () {
        case .unknown :
          return "Unknown"
        case .ok :
          return "Ok"
        case .error :
          return "This part has error(s)"
        case .warning :
          return "This part has warning(s)"
        }
      }catch _ {
        return "This part cannot be read"
      }
    }
  }

  //····················································································································

  func statusImage () -> NSImage? {
    if self.mFullPath.isEmpty {
      return nil
    }else if self.mIsDuplicated || self.mIsAlreadyLoaded {
      return NSImage.statusError
    }else{
      do{
      switch try self.partStatus () {
        case .unknown :
          return NSImage.statusNone
        case .ok :
          return NSImage.statusSuccess
        case .error :
          return NSImage.statusError
        case .warning :
          return NSImage.statusWarning
        }
      }catch _ {
        return NSImage (named: NSImage.Name ("exclamation"))
      }
    }
  }

  //····················································································································

  func partStatus () throws -> MetadataStatus {
    if let s = self.mPartStatus {
      return s
    }else if self.mFullPath != "" {
      self.mPartStatus = .error
      let metadata = try getFileMetadata (atPath: self.mFullPath)
      self.mPartStatus = metadata.metadataStatus
      return metadata.metadataStatus
    }else{
      return .error
    }
  }

  //····················································································································

  func partStatusOk () -> Bool {
    if let s = try? self.partStatus () {
      return s == .ok
    }else{
      return false
    }
  }

 //····················································································································

  var image : NSImage {
    if let image = self.mObjectImage {
      return image
    }else{
      let image = self.buildImage ()
      self.mObjectImage = image
      return image
    }
  }

  //····················································································································

  private func buildImage () -> NSImage {
    var image : NSImage? = nil
    let fm = FileManager ()
    if let data = fm.contents (atPath: self.mFullPath) {
      do{
        let documentData = try loadEasyBindingFile (fromData: data, documentName: self.mFullPath.lastPathComponent, undoManager: nil)
        image = self.mBuildPreviewShapeFunction (documentData.documentRootObject)
      }catch let error {
        let alert = NSAlert (error: error)
        _ = alert.runModal ()
      }
    }
    return image ?? NSImage ()
  }

  //····················································································································

}

//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
