AutoCat/AutoCatCore/Models/Vehicle.swift

420 lines
14 KiB
Swift

import Foundation
import RealmSwift
public class VehicleName: Object, Decodable, Cloneable {
@Persisted public var original: String?
@Persisted public var normalized: String?
public required init(copy: VehicleName) {
self.original = copy.original
self.normalized = copy.normalized
}
required override init() {
}
}
public class VehicleBrand: Object, Decodable, Cloneable {
@Persisted public var name: VehicleName?
@Persisted public var logo: String?
public required init(copy: VehicleBrand) {
self.name = copy.name?.clone()
self.logo = copy.logo
}
required override init() {
super.init()
}
}
public class VehicleModel: Object, Decodable, Cloneable {
@Persisted var name: VehicleName?
public required init(copy: VehicleModel) {
self.name = copy.name?.clone()
}
required override init() {
super.init()
}
}
public class VehicleEngine: Object, Decodable, Cloneable {
@Persisted public var number: String?
@Persisted public var volume: Int? = 0
@Persisted public var powerHp: Float? = 0
@Persisted public var powerKw: Float? = 0
@Persisted public var fuelType: String?
enum CodingKeys: String, CodingKey {
case number, volume, powerHp, powerKw, fuelType
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.number = try container.decodeIfPresent(String.self, forKey: .number)
self.volume = try container.decodeIfPresent(Int.self, forKey: .volume)
self.powerHp = try container.decodeIfPresent(Float.self, forKey: .powerHp)
self.powerKw = try container.decodeIfPresent(Float.self, forKey: .powerKw)
self.fuelType = try container.decodeIfPresent(String.self, forKey: .fuelType)
}
required override init() {
super.init()
}
public required init(copy: VehicleEngine) {
self.number = copy.number
self.volume = copy.volume
self.powerHp = copy.powerHp
self.powerKw = copy.powerKw
self.fuelType = copy.fuelType
}
}
public class VehiclePhoto: Object, Decodable, Cloneable {
@Persisted public var brand: String?
@Persisted public var model: String?
@Persisted public var date: TimeInterval = 0
@Persisted public var url: String = ""
public override var description: String {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier:"GMT")
formatter.dateStyle = .medium
formatter.timeStyle = .none
let date = Date(timeIntervalSince1970: self.date/1000)
let dateStr = formatter.string(from: date)
return "\(self.brand ?? "") \(self.model ?? "") (\(dateStr))"
}
public required init(copy: VehiclePhoto) {
self.brand = copy.brand
self.model = copy.model
self.date = copy.date
self.url = copy.url
}
required override init() {
super.init()
}
}
public enum OwnerType: String, CustomStringConvertible {
case legal
case individual
public var description: String {
switch self {
case .legal: return NSLocalizedString("legal", comment: "Owner type")
case .individual: return NSLocalizedString("individual", comment: "Owner type")
}
}
}
public enum SteeringWheelPosition: CustomStringConvertible {
case left
case right
case unknown
public var description: String {
switch self {
case .left: return "Left"
case .right: return "Right"
case .unknown: return "Unknown"
}
}
}
public class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
@Persisted public var lastOperation: String = ""
@Persisted public var ownerType: String = ""
@Persisted public var from: Int64 = 0
@Persisted public var to: Int64 = 0
@Persisted public var region: String?
@Persisted public var registrationRegion: String?
@Persisted public var locality: String?
@Persisted public var code: String?
@Persisted public var street: String?
@Persisted public var building: String?
@Persisted public var inn: String?
required public init(copy: VehicleOwnershipPeriod) {
self.lastOperation = copy.lastOperation
self.ownerType = copy.ownerType
self.from = copy.from
self.to = copy.to
self.region = copy.region
self.registrationRegion = copy.registrationRegion
self.locality = copy.locality
self.code = copy.code
self.street = copy.street
self.building = copy.building
self.inn = copy.inn
}
required override init() {
super.init()
}
}
public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportable {
@Persisted public var brand: VehicleBrand?
@Persisted public var model: VehicleModel?
@Persisted public var color: String?
@Persisted public var year: Int = 0
@Persisted public var category: String?
@Persisted public var engine: VehicleEngine?
@Persisted private var number: String = ""
@Persisted public var currentNumber: String?
@Persisted public var vin1: String?
@Persisted public var vin2: String?
@Persisted public var sts: String?
@Persisted public var pts: String?
@Persisted public var isRightWheel: Bool?
@Persisted public var isJapanese: Bool?
@Persisted public var addedDate: TimeInterval = 0
@Persisted public var updatedDate: TimeInterval = 0
@Persisted public var addedBy: String = ""
@Persisted public var photos: List<VehiclePhoto>
@Persisted public var ownershipPeriods: List<VehicleOwnershipPeriod>
@Persisted public var events: List<VehicleEvent>
@Persisted public var osagoContracts: List<Osago>
@Persisted public var ads: List<VehicleAd>
@Persisted public var notes: List<VehicleNote>
@Persisted public var debugInfo: DebugInfo?
@Persisted public var synchronized: Bool = true
lazy var formatter: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .medium
f.timeStyle = .medium
return f
}()
enum CodingKeys: String, CodingKey {
case brand
case model
case color
case year
case category
case engine
case number
case currentNumber
case vin1
case vin2
case sts
case pts
case isRightWheel
case isJapanese
case addedDate
case updatedDate
case addedBy
case photos
case ownershipPeriods
case events
case osagoContracts
case ads
case notes
case debugInfo
}
required public init(from decoder: Decoder) throws {
super.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
self.color = try container.decodeIfPresent(String.self, forKey: .color)
self.year = try container.decodeIfPresent(Int.self, forKey: .year) ?? 0
self.category = try container.decodeIfPresent(String.self, forKey: .category)
self.number = try container.decode(String.self, forKey: .number)
self.engine = try container.decodeIfPresent(VehicleEngine.self, forKey: .engine)
self.currentNumber = try container.decodeIfPresent(String.self, forKey: .currentNumber)
self.vin1 = try container.decodeIfPresent(String.self, forKey: .vin1)
self.vin2 = try container.decodeIfPresent(String.self, forKey: .vin2)
self.sts = try container.decodeIfPresent(String.self, forKey: .sts)
self.pts = try container.decodeIfPresent(String.self, forKey: .pts)
self.isRightWheel = try container.decodeIfPresent(Bool.self, forKey: .isRightWheel)
self.isJapanese = try container.decodeIfPresent(Bool.self, forKey: .isJapanese)
self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000
self.addedBy = try container.decode(String.self, forKey: .addedBy)
self.debugInfo = try container.decodeIfPresent(DebugInfo.self, forKey: .debugInfo)
self.updatedDate = (try container.decode(TimeInterval.self, forKey: .updatedDate))/1000
if let photosArray = try container.decodeIfPresent([VehiclePhoto].self, forKey: .photos) {
self.photos.append(objectsIn: photosArray)
}
if let ownersipsArray = try container.decodeIfPresent([VehicleOwnershipPeriod].self, forKey: .ownershipPeriods) {
self.ownershipPeriods.append(objectsIn: ownersipsArray)
}
if let eventsArray = try container.decodeIfPresent([VehicleEvent].self, forKey: .events) {
self.events.append(objectsIn: eventsArray.sorted { $0.date > $1.date })
}
if let osago = try container.decodeIfPresent([Osago].self, forKey: .osagoContracts) {
self.osagoContracts.append(objectsIn: osago)
}
if let ads = try container.decodeIfPresent([VehicleAd].self, forKey: .ads) {
self.ads.append(objectsIn: ads)
}
if let notes = try container.decodeIfPresent([VehicleNote].self, forKey: .notes) {
self.notes.append(objectsIn: notes)
}
// All vehicles received from API are synchronized by definition
self.synchronized = true
}
required override init() {
super.init()
}
public init(_ number: String) {
super.init()
self.number = number
self.addedDate = Date().timeIntervalSince1970
self.updatedDate = self.addedDate
self.synchronized = false
}
public func getNumber() -> String {
return self.number
}
public override static func primaryKey() -> String? {
return "number"
}
public override class func ignoredProperties() -> [String] {
return ["id", "identifier", "differenceIdentifier", "formatter"]
}
public var unrecognized: Bool {
return self.brand == nil
}
public var outdated: Bool {
if let current = self.currentNumber {
return current != self.number
} else {
return false
}
}
// MARK: - Cloneable
required public init(copy: Vehicle) {
self.brand = copy.brand
self.model = copy.model
self.color = copy.color
self.year = copy.year
self.category = copy.category
self.engine = copy.engine
self.number = copy.number
self.currentNumber = copy.currentNumber
self.vin1 = copy.vin1
self.vin2 = copy.vin2
self.sts = copy.sts
self.pts = copy.pts
self.isRightWheel = copy.isRightWheel
self.isJapanese = copy.isJapanese
self.addedDate = copy.addedDate
self.addedBy = copy.addedBy
self.updatedDate = copy.updatedDate
let photos = List<VehiclePhoto>()
photos.append(objectsIn: copy.photos.map { $0.clone() })
self.photos = photos
let ownerships = List<VehicleOwnershipPeriod>()
ownerships.append(objectsIn: copy.ownershipPeriods.map { $0.clone() })
self.ownershipPeriods = ownerships
let events = List<VehicleEvent>()
events.append(objectsIn: copy.events.map { $0.clone() }.sorted { $0.date > $1.date })
self.events = events
let osago = List<Osago>()
osago.append(objectsIn: copy.osagoContracts.map { $0.clone() })
self.osagoContracts = osago
let ads = List<VehicleAd>()
ads.append(objectsIn: copy.ads.map { $0.clone() })
self.ads = ads
let notes = List<VehicleNote>()
notes.append(objectsIn: copy.notes.map { $0.clone() })
self.notes = notes
self.synchronized = copy.synchronized
}
public func shallowClone() -> Vehicle {
let vehicle = Vehicle()
vehicle.number = self.number
vehicle.currentNumber = self.currentNumber
vehicle.addedDate = self.addedDate
vehicle.updatedDate = self.updatedDate
vehicle.brand = self.brand
vehicle.synchronized = self.synchronized
let notes = List<VehicleNote>()
notes.append(objectsIn: self.notes.map { $0.clone() })
vehicle.notes = notes
let events = List<VehicleEvent>()
events.append(objectsIn: self.events.map { $0.clone() }.sorted { $0.date > $1.date })
vehicle.events = events
return vehicle
}
// MARK: - Exportable
public static var csvHeader: String {
return "Plate Number, Model, Color, Year, Power (HP), Events, Owners, VIN, STS, PTS, Engine number, Added Date, Updated date, Locations, Notes"
}
public var csvLine: String {
let model = self.brand?.name?.original ?? "<unknown>"
let added = self.formatter.string(from: Date(timeIntervalSince1970: self.addedDate))
let updated = self.formatter.string(from: Date(timeIntervalSince1970: self.updatedDate))
var eventsString = ""
for event in events {
let location = event.address ?? "lat: \(event.latitude), lon: \(event.longitude)"
let date = formatter.string(from: Date(timeIntervalSince1970: event.date))
eventsString.append(location + "; " + date + "\r\n")
}
let notesString = self.notes.reduce("") { partialResult, note in
partialResult + note.text + "\r\n"
}
let number = self.currentNumber == nil ? self.number : "\(self.number) (\(self.currentNumber ?? ""))"
return String(format: "%@, \"%@\", %@, %d, %f, %d, %d, %@, %@, %@, %@, \"%@\", \"%@\", \"%@\", \"%@\"",
number,
model,
self.color ?? "",
self.year,
self.engine?.powerHp ?? 0.0,
self.events.count,
self.ownershipPeriods.count,
self.vin1 ?? "",
self.sts ?? "",
self.pts ?? "",
self.engine?.number ?? "",
added,
updated,
eventsString,
notesString)
}
}