<commit_before>//
//  TKNamedCoordinate.swift
//  SkedGoKit
//
//  Created by <NAME> on 25/10/16.
//  Copyright © 2016 SkedGo. All rights reserved.
//

import Foundation
import MapKit

open class TKNamedCoordinate : NSObject, NSSecureCoding, Codable, TKClusterable {
  
  public fileprivate(set) var coordinate: CLLocationCoordinate2D {
    didSet {
      _address = nil
      _placemark = nil
    }
  }
  
  @objc
  public var clusterIdentifier: String? = nil
  
  @objc public var name: String? = nil
  
  @objc public var _address: String? = nil
  @objc public var address: String? {
    get {
      // this will call the lazy placemark getter, which will set the address
      guard _address == nil, let placemark = self.placemark else { return _address }
      
      _address = TKLocationHelper.address(for: placemark)
      return _address
    }
    set {
      _address = newValue
    }
  }
  
  @objc public var data: [String: Any] = [:]
  
  private var _placemark: CLPlacemark? = nil
  @objc public var placemark: CLPlacemark? {
    if let placemark = _placemark { return placemark }
    guard coordinate.isValid else { return nil }
    
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    let geocoder = CLGeocoder()
    
    // no weak self as we're not retaining the geocoder
    geocoder.reverseGeocodeLocation(location) { placemarks, error in
      guard let placemark = placemarks?.first else { return }
      
      self._placemark = placemark
      self._address = TKLocationHelper.address(for: placemark)
      
      // KVO
      if self.name != nil {
        self.subtitle = ""
      } else {
        self.title = ""
      }
    }
    
    return _placemark
  }
  
  @objc public var locationID: String? = nil
  
  @objc public var isDraggable: Bool = false
  
  @objc public var isSuburb: Bool = false
  
  /// - note: Fails if annotation does not have a valid coordinate.
  @objc(namedCoordinateForAnnotation:)
  public static func namedCoordinate(for annotation: MKAnnotation) -> TKNamedCoordinate? {
    if let named = annotation as? TKNamedCoordinate {
      return named
    }
    
    let coordinate = annotation.coordinate
    guard coordinate.isValid else {
      return nil
    }
    
    if let name = annotation.title ?? nil,
       let address = annotation.subtitle ?? nil {
      return TKNamedCoordinate(latitude: coordinate.latitude, longitude: coordinate.longitude, name: name, address: address)
    } else {
      return TKNamedCoordinate(coordinate: coordinate)
    }
  }
  
  @objc public init(latitude: CLLocationDegrees, longitude: CLLocationDegrees, name: String?, address: String?) {
    coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    self.name = name
    _address = address
  }
  
  @objc public init(placemark: CLPlacemark) {
    coordinate = placemark.location?.coordinate ?? kCLLocationCoordinate2DInvalid
    name = TKLocationHelper.name(from: placemark)
    _address = TKLocationHelper.address(for: placemark)
    _placemark = placemark
  }
  
  @objc public init(coordinate: CLLocationCoordinate2D) {
    self.coordinate = coordinate
  }
  
  @objc
  public init(name: String?, address: String?) {
    self.coordinate = kCLLocationCoordinate2DInvalid
    self.name = name
    _address = address
  }
  
  convenience init(from: TKAPI.Location) {
    self.init(latitude: from.lat, longitude: from.lng, name: from.name, address: from.address)
  }

  // MARK: - Codable
  
  private enum CodingKeys: String, CodingKey {
    case latitude
    case longitude
    case lat
    case lng
    case name
    case address
    case locationID
    case data
    case placemark
    case isDraggable
    case isSuburb
    case clusterIdentifier
  }
  
  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    // We support both lat/lng and latitude/longitude spellings, making this ugly
    let latitude: CLLocationDegrees
    if let degrees = try? container.decode(CLLocationDegrees.self, forKey: .latitude) {
      latitude = degrees
    } else {
      latitude = try container.decode(CLLocationDegrees.self, forKey: .lat)
    }
    let longitude: CLLocationDegrees
    if let degrees = try? container.decode(CLLocationDegrees.self, forKey: .longitude) {
      longitude = degrees
    } else {
      longitude = try container.decode(CLLocationDegrees.self, forKey: .lng)
    }
    coordinate = CLLocationCoordinate2DMake(latitude, longitude)

    // All of these are often not provide, hence `try?` everywhere
    name = try? container.decode(String.self, forKey: .name)
    _address = try? container.decode(String.self, forKey: .address)
    locationID = try? container.decode(String.self, forKey: .locationID)
    clusterIdentifier = try? container.decode(String.self, forKey: .clusterIdentifier)
    isDraggable = (try? container.decode(Bool.self, forKey: .isDraggable)) ?? false
    isSuburb = (try? container.decode(Bool.self, forKey: .isSuburb)) ?? false

    if let encodedData = try? container.decode(Data.self, forKey: .data), let data = try JSONSerialization.jsonObject(with: encodedData, options: []) as? [String: Any] {
      self.data = data
    } else {
      self.data = [:]
    }
  }
  
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(coordinate.latitude, forKey: .latitude)
    try container.encode(coordinate.longitude, forKey: .longitude)
    try container.encode(name, forKey: .name)
    try container.encode(address, forKey: .address)
    try container.encode(locationID, forKey: .locationID)
    try container.encode(clusterIdentifier, forKey: .clusterIdentifier)
    try container.encode(isDraggable, forKey: .isDraggable)
    try container.encode(isSuburb, forKey: .isSuburb)

    if let sanitized = TKJSONSanitizer.sanitize(data) {
      let encodedData = try JSONSerialization.data(withJSONObject: sanitized, options: [])
      try container.encode(encodedData, forKey: .data)
    }
  }
  
  // MARK: - NSSecureCoding
  
  @objc public class var supportsSecureCoding: Bool { true }
  
  @objc
  public required init?(coder aDecoder: NSCoder) {
    let latitude = aDecoder.decodeDouble(forKey: "latitude")
    let longitude = aDecoder.decodeDouble(forKey: "longitude")
    if abs(latitude) < 0.01, abs(longitude) < 0.01 {
      return nil // possiblity from when they weren't safely encoded
    }
    coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    name = aDecoder.decodeObject(of: NSString.self, forKey: "name") as String?
    _address = aDecoder.decodeObject(of: NSString.self, forKey: "address") as String?
    locationID = aDecoder.decodeObject(of: NSString.self, forKey: "locationID") as String?
    clusterIdentifier = aDecoder.decodeObject(of: NSString.self, forKey: "clusterIdentifier") as String?
    _placemark = aDecoder.decodeObject(of: CLPlacemark.self, forKey: "placemark")
    isDraggable = aDecoder.decodeBool(forKey: "isDraggable")
    isSuburb = aDecoder.decodeBool(forKey: "isSuburb")
    data = aDecoder.decodeObject(of: [NSDictionary.self], forKey: "data") as? [String: Any] ?? [:]
  }
  
  @objc(encodeWithCoder:)
  open func encode(with aCoder: NSCoder) {
    aCoder.encode(coordinate.latitude, forKey: "latitude")
    aCoder.encode(coordinate.longitude, forKey: "longitude")<commit_msg>Fix named coordinates not being decoded from old format<commit_after>//
//  TKNamedCoordinate.swift
//  SkedGoKit
//
//  Created by <NAME> on 25/10/16.
//  Copyright © 2016 SkedGo. All rights reserved.
//

import Foundation
import MapKit

open class TKNamedCoordinate : NSObject, NSSecureCoding, Codable, TKClusterable {
  
  public fileprivate(set) var coordinate: CLLocationCoordinate2D {
    didSet {
      _address = nil
      _placemark = nil
    }
  }
  
  @objc
  public var clusterIdentifier: String? = nil
  
  @objc public var name: String? = nil
  
  @objc public var _address: String? = nil
  @objc public var address: String? {
    get {
      // this will call the lazy placemark getter, which will set the address
      guard _address == nil, let placemark = self.placemark else { return _address }
      
      _address = TKLocationHelper.address(for: placemark)
      return _address
    }
    set {
      _address = newValue
    }
  }
  
  @objc public var data: [String: Any] = [:]
  
  private var _placemark: CLPlacemark? = nil
  @objc public var placemark: CLPlacemark? {
    if let placemark = _placemark { return placemark }
    guard coordinate.isValid else { return nil }
    
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    let geocoder = CLGeocoder()
    
    // no weak self as we're not retaining the geocoder
    geocoder.reverseGeocodeLocation(location) { placemarks, error in
      guard let placemark = placemarks?.first else { return }
      
      self._placemark = placemark
      self._address = TKLocationHelper.address(for: placemark)
      
      // KVO
      if self.name != nil {
        self.subtitle = ""
      } else {
        self.title = ""
      }
    }
    
    return _placemark
  }
  
  @objc public var locationID: String? = nil
  
  @objc public var isDraggable: Bool = false
  
  @objc public var isSuburb: Bool = false
  
  /// - note: Fails if annotation does not have a valid coordinate.
  @objc(namedCoordinateForAnnotation:)
  public static func namedCoordinate(for annotation: MKAnnotation) -> TKNamedCoordinate? {
    if let named = annotation as? TKNamedCoordinate {
      return named
    }
    
    let coordinate = annotation.coordinate
    guard coordinate.isValid else {
      return nil
    }
    
    if let name = annotation.title ?? nil,
       let address = annotation.subtitle ?? nil {
      return TKNamedCoordinate(latitude: coordinate.latitude, longitude: coordinate.longitude, name: name, address: address)
    } else {
      return TKNamedCoordinate(coordinate: coordinate)
    }
  }
  
  @objc public init(latitude: CLLocationDegrees, longitude: CLLocationDegrees, name: String?, address: String?) {
    coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    self.name = name
    _address = address
  }
  
  @objc public init(placemark: CLPlacemark) {
    coordinate = placemark.location?.coordinate ?? kCLLocationCoordinate2DInvalid
    name = TKLocationHelper.name(from: placemark)
    _address = TKLocationHelper.address(for: placemark)
    _placemark = placemark
  }
  
  @objc public init(coordinate: CLLocationCoordinate2D) {
    self.coordinate = coordinate
  }
  
  @objc
  public init(name: String?, address: String?) {
    self.coordinate = kCLLocationCoordinate2DInvalid
    self.name = name
    _address = address
  }
  
  convenience init(from: TKAPI.Location) {
    self.init(latitude: from.lat, longitude: from.lng, name: from.name, address: from.address)
  }

  // MARK: - Codable
  
  private enum CodingKeys: String, CodingKey {
    case latitude
    case longitude
    case lat
    case lng
    case name
    case address
    case locationID
    case data
    case placemark
    case isDraggable
    case isSuburb
    case clusterIdentifier
  }
  
  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    // We support both lat/lng and latitude/longitude spellings, making this ugly
    let latitude: CLLocationDegrees
    if let degrees = try? container.decode(CLLocationDegrees.self, forKey: .latitude) {
      latitude = degrees
    } else {
      latitude = try container.decode(CLLocationDegrees.self, forKey: .lat)
    }
    let longitude: CLLocationDegrees
    if let degrees = try? container.decode(CLLocationDegrees.self, forKey: .longitude) {
      longitude = degrees
    } else {
      longitude = try container.decode(CLLocationDegrees.self, forKey: .lng)
    }
    coordinate = CLLocationCoordinate2DMake(latitude, longitude)

    // All of these are often not provide, hence `try?` everywhere
    name = try? container.decode(String.self, forKey: .name)
    _address = try? container.decode(String.self, forKey: .address)
    locationID = try? container.decode(String.self, forKey: .locationID)
    clusterIdentifier = try? container.decode(String.self, forKey: .clusterIdentifier)
    isDraggable = (try? container.decode(Bool.self, forKey: .isDraggable)) ?? false
    isSuburb = (try? container.decode(Bool.self, forKey: .isSuburb)) ?? false

    if let encodedData = try? container.decode(Data.self, forKey: .data), let data = try JSONSerialization.jsonObject(with: encodedData, options: []) as? [String: Any] {
      self.data = data
    } else {
      self.data = [:]
    }
  }
  
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(coordinate.latitude, forKey: .latitude)
    try container.encode(coordinate.longitude, forKey: .longitude)
    try container.encode(name, forKey: .name)
    try container.encode(address, forKey: .address)
    try container.encode(locationID, forKey: .locationID)
    try container.encode(clusterIdentifier, forKey: .clusterIdentifier)
    try container.encode(isDraggable, forKey: .isDraggable)
    try container.encode(isSuburb, forKey: .isSuburb)

    if let sanitized = TKJSONSanitizer.sanitize(data) {
      let encodedData = try JSONSerialization.data(withJSONObject: sanitized, options: [])
      try container.encode(encodedData, forKey: .data)
    }
  }
  
  // MARK: - NSSecureCoding
  
  @objc public class var supportsSecureCoding: Bool { true }
  
  @objc
  public required init?(coder aDecoder: NSCoder) {
    if let data = aDecoder.decodeData() {
      // For backwards compatibility
      do {
        let decoded = try JSONDecoder().decode(TKNamedCoordinate.self, from: data)
        self.coordinate = decoded.coordinate
        self.name = decoded.name
        self._address = decoded.address
        self.locationID = decoded.locationID
        self.clusterIdentifier = decoded.clusterIdentifier
        self.data = decoded.data
        self.isSuburb = decoded.isSuburb
        self.isDraggable = decoded.isDraggable
      } catch {
        assertionFailure("Couldn't decode due to: \(error)")
        return nil
      }

    } else {
      // The new way, supporting secure coding
      let latitude = aDecoder.decodeDouble(forKey: "latitude")
      let longitude = aDecoder.decodeDouble(forKey: "longitude")
      if abs(latitude) < 0.01, abs(longitude) < 0.01 {
        return nil // possiblity from when they weren't safely encoded
      }
      coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
      name = aDecoder.decodeObject(of: NSString.self, forKey: "name") as String?
      _address = aDecoder.decodeObject(of: NSString.self, forKey: "address") as String?
      locationID = aDecoder.decodeObject(of: NSString.self, forKey: "locationID") as String?
      clusterIdentifier = aDecoder.decodeObject(of: NSString.self, forKey: "clusterIdentifier") as String?
      _placemark = aDecoder.decodeObject(of: CLPlacemark.self, forKey: "placemark")
      isDraggable = aDecoder.decodeBool(forKey: "isDraggable")
      isSuburb = aDecoder.decodeBool(forKey: "isSuburb")
      data = aDecoder.decodeObject(of: [NSDictionary.self], forKey: "data") as? [String: Any] ?? [:]
    }
  }
  
  @objc(encodeWithCoder:)
  open func encode(with aCoder: NSCoder) {
    aCoder.encode(coordinate.latitude, forKey: "latitude")
    aCoder.encode(coordinate.longitude, forKey: "longitude")
{'fraction_non_alphanumeric': 0.09609858885128664, 'fraction_numerical': 0.002937232616052615, 'mean_word_length': 3.250203527815468, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3630100', 'n_tokens_mistral': 4920, 'n_tokens_neox': 4823, 'n_words': 1262}