//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
//  THIS FILE IS GENERATED BY EASY BINDINGS, DO NOT MODIFY IT
//——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

import Cocoa

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

final class ParallelObjectSetupContext {
  private let mOperationQueue = OperationQueue ()
  private var mToOneSetUpOperationList = [() -> Void] ()
  private let mMutexToOne = DispatchSemaphore (value: 1)
  private var mToManySetUpOperationList = [() -> Void] ()
  private let mMutexToMany = DispatchSemaphore (value: 1)
  private var mOperationQueueCount = 0

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

  func addOperation (_ inOperation : @escaping () -> Void) {
    self.mOperationQueueCount += 1
    self.mOperationQueue.addOperation (inOperation)
  }

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

  fileprivate var operationQueueCount : Int { return self.mOperationQueueCount }

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

  func addToOneSetupDeferredOperation (_ inDeferredOperation : @escaping () -> Void) {
    self.mMutexToOne.wait ()
    self.mToOneSetUpOperationList.append (inDeferredOperation)
    self.mMutexToOne.signal ()
  }

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

  func addToManySetupDeferredOperation (_ inDeferredOperation : @escaping () -> Void) {
    self.mMutexToMany.wait ()
    self.mToManySetUpOperationList.append (inDeferredOperation)
    self.mMutexToMany.signal ()
  }

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

  fileprivate func waitUntilAllOperationsAreFinished () {
    self.mOperationQueue.waitUntilAllOperationsAreFinished ()
  }

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

  fileprivate var toOneSetupOperationCount : Int { return self.mToOneSetUpOperationList.count }

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

  fileprivate func performToOneSetupOperations () {
    for toOneSetupOperation in self.mToOneSetUpOperationList {
      toOneSetupOperation ()
    }
  }

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

  fileprivate var toManySetupOperationCount : Int { return self.mToManySetUpOperationList.count }

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

  fileprivate func performToManySetupOperations () {
    for toManySetupOperation in self.mToManySetUpOperationList {
      toManySetupOperation ()
    }
  }

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

}

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

func loadEasyBindingTextFile (_ inUndoManager : EBUndoManager?,
                              documentName inDocumentName : String,
                              from ioDataScanner: inout EBDataScanner) throws -> EBDocumentData {
  appendDocumentFileOperationInfo ("Read Text Document file: \(inDocumentName)\n")
  var operationStartDate = Date ()
  let startDate = operationStartDate
//--- Check header ends with line feed
  ioDataScanner.acceptRequired (byte: ASCII.lineFeed.rawValue)
//--- Read Status
  let metadataStatus = UInt8 (ioDataScanner.parseBase62EncodedInt ())
 // Swift.print ("metadataStatus \(metadataStatus)")
//--- Read metadata dictionary
  let metadataDictionary : [String : Any] = try ioDataScanner.parseJSON ()
 // Swift.print ("metadataDictionary \(metadataDictionary)")
//--- Read classes
  var classDefinition = [(String, [String])] ()
  while ioDataScanner.testAccept (byte: ASCII.dollar.rawValue) {
    let className = try ioDataScanner.parseString ()
    var readPropertyNames = true
    var propertyNameArray = [String] ()
    while readPropertyNames, ioDataScanner.ok () {
      if ioDataScanner.test (byte: ASCII.dollar.rawValue) {
        readPropertyNames = false
      }else if ioDataScanner.test (byte: ASCII.at.rawValue) {
        readPropertyNames = false
      }else{
        let propertyName = try ioDataScanner.parseString ()
        propertyNameArray.append (propertyName)
      }
    }
    classDefinition.append ((className, propertyNameArray))
  }
  appendDocumentFileOperationInfo ("  Read \(classDefinition.count) classes: \(Int (Date ().timeIntervalSince (operationStartDate) * 1000.0)) ms\n")
  operationStartDate = Date ()
//--- Read objects
  var objectArray = [EBManagedObject] ()
  var propertyValueArray = [[String : NSRange]] ()
  while !ioDataScanner.eof (), ioDataScanner.testAccept (byte: ASCII.at.rawValue) {
    let classIndex = ioDataScanner.parseBase62EncodedInt ()
    let propertyNameArray = classDefinition [classIndex].1
    let className = classDefinition [classIndex].0
    let managedObject = newInstanceOfEntityNamed (inUndoManager, className)!
    objectArray.append (managedObject)
    var readPropertyValues = true
    var valueDictionary = [String : NSRange] ()
    var propertyIndex = 0
    while readPropertyValues, ioDataScanner.ok () {
      if ioDataScanner.test (byte: ASCII.at.rawValue) {
        readPropertyValues = false
      }else if ioDataScanner.eof () {
        readPropertyValues = false
      }else{
        let propertyRange = ioDataScanner.getLineRangeAndAdvance ()
        valueDictionary [propertyNameArray [propertyIndex]] = propertyRange
        propertyIndex += 1
        // Swift.print ("  PROPERTY VALUE: '\(propertyValue)'")
      }
    }
    propertyValueArray.append (valueDictionary)
  }
  appendDocumentFileOperationInfo ("  Read \(objectArray.count) objects: \(Int (Date ().timeIntervalSince (operationStartDate) * 1000.0)) ms\n")
  operationStartDate = Date ()
//--- Prepare objects
  var idx = 0
  let parallelObjectSetupContext = ParallelObjectSetupContext ()
  for managedObject in objectArray {
    let valueDictionary = propertyValueArray [idx]
    idx += 1
    managedObject.setUpWithTextDictionary (valueDictionary, objectArray, ioDataScanner.data, parallelObjectSetupContext)
  }
  parallelObjectSetupContext.waitUntilAllOperationsAreFinished ()
  appendDocumentFileOperationInfo ("  Prepare objects: \(Int (Date ().timeIntervalSince (operationStartDate) * 1000.0)) ms (\(parallelObjectSetupContext.operationQueueCount) operations)\n")
  operationStartDate = Date ()
//--- Setup toOne
  parallelObjectSetupContext.performToOneSetupOperations ()
  appendDocumentFileOperationInfo ("  Setup toOne: \(Int (Date ().timeIntervalSince (operationStartDate) * 1000.0)) ms (\(parallelObjectSetupContext.toOneSetupOperationCount) operations)\n")
  operationStartDate = Date ()
//--- Setup toMany
  parallelObjectSetupContext.performToManySetupOperations ()
  appendDocumentFileOperationInfo ("  Setup toMany: \(Int (Date ().timeIntervalSince (operationStartDate) * 1000.0)) ms (\(parallelObjectSetupContext.toManySetupOperationCount) operations)\n")
  appendDocumentFileOperationInfo ("Total duration: \(Int (Date ().timeIntervalSince (startDate) * 1000.0)) ms\n\n")
//--- Scanner error ?
  if !ioDataScanner.ok () {
    let dictionary = [
      "Cannot Open Document" : NSLocalizedDescriptionKey,
      "The file has an invalid format" : NSLocalizedRecoverySuggestionErrorKey
    ]
    throw NSError (domain: Bundle.main.bundleIdentifier!, code: 1, userInfo: dictionary)
  }
//--- Analyze read data
  if ioDataScanner.ok () && (objectArray.count > 0) {
    let rootObject = objectArray [0]
    return EBDocumentData (
      documentMetadataStatus: metadataStatus,
      documentMetadataDictionary: metadataDictionary,
      documentRootObject: rootObject,
      documentFileFormat: .textual
    )
  }else{
    let dictionary = [
      "Cannot Open Document" :  NSLocalizedDescriptionKey,
      "Root object cannot be read" :  NSLocalizedRecoverySuggestionErrorKey
    ]
    throw NSError (domain: Bundle.main.bundleIdentifier!, code: 1, userInfo: dictionary)
  }
}

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

func dataForTextualSaveOperation (from inDocumentData : EBDocumentData) throws -> Data {
//--- First line: PM-TEXT-FORMAT
  var fileStringData = Data ()
  fileStringData.append (ascii: .P)
  fileStringData.append (ascii: .M)
  fileStringData.append (ascii: .minus)
  fileStringData.append (ascii: .T)
  fileStringData.append (ascii: .E)
  fileStringData.append (ascii: .X)
  fileStringData.append (ascii: .T)
  fileStringData.append (ascii: .minus)
  fileStringData.append (ascii: .F)
  fileStringData.append (ascii: .O)
  fileStringData.append (ascii: .R)
  fileStringData.append (ascii: .M)
  fileStringData.append (ascii: .A)
  fileStringData.append (ascii: .T)
  fileStringData.append (ascii: .lineFeed)
//--- Append status
  fileStringData.append (base62Encoded: Int (inDocumentData.documentMetadataStatus))
  fileStringData.append (ascii: .lineFeed)
//--- Append metadata dictionary
  // .sortedKeys is only available in 10.13
  let textMetaData = try JSONSerialization.data (withJSONObject: inDocumentData.documentMetadataDictionary, options: [])
  fileStringData += textMetaData
  fileStringData.append (ascii: .lineFeed)
//--- Build class index dictionary
  let objectArray = collectAndPrepareObjectsForSaveOperation (fromRoot: inDocumentData.documentRootObject)
  var classDictionary = [String : Int] ()
  var classDescriptionString = ""
  for object in objectArray {
    let key = String (describing: type (of: object as Any))
    if classDictionary [key] == nil {
      classDictionary [key] = classDictionary.count
      classDescriptionString += "$" + key + "\n"
      object.appendPropertyNamesTo (&classDescriptionString)
    }
  }
  fileStringData += classDescriptionString.data (using: .utf8)!
//--- Save data
  for object in objectArray {
    let key = String (describing: type (of: object as Any))
    let classIndex = classDictionary [key]!
    fileStringData.append (ascii: .at)
    fileStringData.append (base62Encoded: classIndex)
    fileStringData.append (ascii: .lineFeed)
    object.appendPropertyValuesTo (&fileStringData)
  }
//---
//   Swift.print ("Text Saving \(Int (Date ().timeIntervalSince (start) * 1000.0)) ms")
  return fileStringData
}

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