/*
 * LexicalModelRepository.swift
 * FirstVoices app
 *
 * License: MIT
 *
 * Copyright © 2022 FirstVoices.
 *
 * Created by Shawn Schantz on 2022-01-27.
 *
 * Class to call the Keyman API to query about lexical models for a given language.
 *
 */

import UIKit
import KeymanEngine

class LexicalModelRepository {

  private static let keymanLexicalModelApiUrl = "https://api.keyman.com/model?q=bcp47:"
  
  // these strings match those in KeymanEngine so that we can share data across UserDefaults (hack!)
  private static let userPredictionSettings = "UserPredictionEnablementSettings"
  private static let userCorrectionSettings = "UserCorrectionEnablementSettings"
  
  static let shared: LexicalModelRepository  = {
    let instance = LexicalModelRepository()
    return instance
  }()
    
  private init() {}

  func getAvailableLexicalModels(languageTag: String) -> [FVLexicalModel] {
    var modelArray: [FVLexicalModel] = []
    let keymanApiUrl: URL = URL.init(string: "\(LexicalModelRepository.keymanLexicalModelApiUrl)\(languageTag)")!
    
    do {
      let lexicalModelData = try Data(contentsOf: keymanApiUrl, options: NSData.ReadingOptions())

      do {
          // make sure this JSON is in the format we expect
          if let jsonArray = try JSONSerialization.jsonObject(with: lexicalModelData, options: []) as? [[String : Any]] {
              // try to read array
            if !jsonArray.isEmpty {
              let jsonModelMap = jsonArray.first
              let name = jsonModelMap!["name"] as! String
              let modelId = jsonModelMap!["id"] as! String
              let packageUrl = jsonModelMap!["packageFilename"] as! String
              let version = jsonModelMap!["version"] as! String
              let model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
              modelArray.append(model)
            }
          }
      } catch let error as NSError {
          print("Failed to load: \(error.localizedDescription)")
      }
      print(lexicalModelData)
    } catch {
        print(error)
    }
    
    // TODO: delete commented-out test code when a keyboard with multiple dictionaries is available
    /*
    // test code for multi dictionary support since no real data exists for this
    // add two fake dictionaries for single language
    if (languageTag == "ikt-latn") {
      var name = "northern dialect"
      var modelId = "nrc.str.sencoten"
      var packageUrl = "https://keyman.com/go/package/download/model/nrc.str.sencoten?version=1.0.5&update=1"
      var version = "1.01"
      var model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
      modelArray.append(model)
      name = "southern dialect"
      modelId = "nrc.str.sencoten"
      packageUrl = "https://keyman.com/go/package/download/model/nrc.str.sencoten?version=1.0.5&update=1"
      version = "1.03"
      model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
      modelArray.append(model)
    }
     */
    return modelArray
  }
  
  /*
   * install = 1) download lexical model and 2) turn on prediction and corrections by default
   */
  func installLexicalModel(package: LexicalModelKeymanPackage, keyboardState: KeyboardState, modelId: String) -> Bool {
    let added = Manager.shared.addLexicalModel(lexicalModelId: modelId, languageId: keyboardState.languageTag, from: package)

    if (!added) {
      return false
    }
    
    // set as preferred lexical model (same as done in Keyman switchLexicalModel())
    let defaults: UserDefaults = FVShared.userDefaults()
    defaults.set(preferredLexicalModelID: modelId, forKey: keyboardState.languageTag)

    // call Keyman once after enabling both prediction and correction flags in UserDefaults
    self.writePredictionSettings(languageId: keyboardState.languageTag, modelId: modelId, on: true)
    self.writeCorrectionSettings(languageId: keyboardState.languageTag, modelId: modelId, on: true)

    let applied = self.applyLexicalModelSettings(languageId: keyboardState.languageTag, modelId: modelId)
    
    return applied
  }
  
  func disableLexicalModel(keyboardState: KeyboardState, modelId: String) -> Bool {
    let removed = Manager.shared.removeLexicalModel(lexicalModelId: modelId, languageId: keyboardState.languageTag)
    return removed
  }
  
  func downloadModel(keyboardState: KeyboardState, modelId: String) {
    Manager.shared.downloadLexicalModel(withID: modelId, languageID: keyboardState.languageTag,
                        isUpdate: true, fetchRepositoryIfNeeded: true)
  }
  
  // TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
  /*
   * write prediction settings to UserDefaults but do not apply
   */
  func writePredictionSettings(languageId: String, modelId: String, on: Bool) {
    self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userPredictionSettings,
                              languageId: languageId, modelId: modelId, on: on)
  }

  // TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
  /*
   * write correction settings to UserDefaults but do not apply
   */
  func writeCorrectionSettings(languageId: String, modelId: String, on: Bool) {
    self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
                              languageId: languageId, modelId: modelId, on: on)
  }

  // TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
  /*
   * write prediction settings to UserDefaults and apply them
   */
  func applyPredictionSettings(languageId: String, modelId: String, on: Bool) -> Bool {
    self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userPredictionSettings,
                              languageId: languageId, modelId: modelId, on: on)
    
    // automatically turn off corrections when predictions are turned off
    if (!on) {
      self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
                                languageId: languageId, modelId: modelId, on: false)

    }
    return self.applyLexicalModelSettings(languageId: languageId, modelId: modelId)
 }

  // TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
  /*
   * write correction settings to UserDefaults and apply them
   */
  func applyCorrectionSettings(languageId: String, modelId: String, on: Bool) -> Bool {
    self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
                              languageId: languageId, modelId: modelId, on: on)
    return self.applyLexicalModelSettings(languageId: languageId, modelId: modelId)
}

  // TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
  /*
   * method to update prediction or correction UserDefaults as specified
   */
  func writeLexicalModelSettings(userDefaultsKey: String, languageId: String, modelId: String, on: Bool) {
    var newSettings: [String:Bool] = [:]
    
    // get current settings for all dictionaries, if they exist in UserDefaults
    let sharedData: UserDefaults = FVShared.userDefaults()
    
    if let settings = sharedData.dictionary(forKey: userDefaultsKey) as? [String : Bool] {
      if settings[languageId] != nil {
        newSettings = settings
      }
    }
    newSettings[languageId] = on
    
    sharedData.set(newSettings, forKey: userDefaultsKey)
  }

  // TODO: delete call to registerLexicalModel, replace with direct API call
 /*
   * call to KeymanEngine registerLexicalModel to force new settings to be applied
   */
  func applyLexicalModelSettings(languageId: String, modelId: String) -> Bool {
    let registered = Manager.shared.registerLexicalModel(lexicalModelId: modelId, languageId: languageId)
    if (!registered) {
      print("Could not register lexical model for prediction settings; lang=\(languageId),  lexical model ID=\(modelId)")
    }
    return registered
  }
}

  class FVLexicalModel {
    let name: String
    let id: String
    let packageUrl: URL
    let languageTag: String
    let version: String

    internal init(name: String, id: String, packageUrl: String, languageTag: String, version: String) {
      self.name = name
      self.id = id
      self.packageUrl = URL(string: packageUrl)!
      self.languageTag = languageTag
      self.version = version
    }
  }
