//
//  Location.swift
//  1Weather
//
//  Created by Demid Merzlyakov on 19.02.2021.
//

import Foundation
import CoreLocation

public struct Location: Equatable, Hashable {
    // MARK: - Data fields
    public var lastTimeUpdated: Date
    public var coordinates: CLLocationCoordinate2D?
    public var imageName: String? = "ny_bridge" //we'll possibly need to switch to URL
    
    public var countryCode: String? {
        didSet {
            if countryCode == "USA" {
                countryCode = "US"
            }
        }
    }
    public var countryName: String? {
        didSet {
            if let newCountryName = countryName {
                if newCountryName != oldValue {
                    if newCountryName == "USA" || newCountryName == "US" || newCountryName.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() == "UNITED STATES OF AMERICA" {
                        if countryCode != "US" {
                            countryCode = "US"
                        }
                    }
                }
            }
        }
    }
    public var region: String? // also, state
    public var cityName: String?
    public var nickname: String? // nickname given to this location by the user
    public var zip: String?
    
    public var fipsCode: String?
    public var timeZone: TimeZone
    
    public var today: CurrentWeather?
    public var daily = [DailyWeather]()
    public var hourly = [HourlyWeather]() {
        didSet {
            let calendar = Calendar.timeZoneCalendar(timeZone: self.timeZone)
            
            dayTimeForecast = hourly.compactMap {
                guard let dayTime = DayTime(hour: calendar.component(.hour, from: $0.date)) else {
                    return nil
                }
                return DayTimeWeather(dayTime: dayTime, hourly: $0)
            }
        }
    }
    public private (set) var dayTimeForecast: [DayTimeWeather] = [DayTimeWeather]()
    
    // MARK: - Derived fields
    public var cityId: String {
        return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")"
    }
}

// MARK: - CustomStringConvertible implementation
extension Location: CustomStringConvertible {
    public var description: String {
        if let coordinates = self.coordinates {
            return String(format: "%@ (%.3f, %.3f)", cityId, coordinates.latitude, coordinates.longitude)
        }
        else {
            return "\(cityId) (no coordinates)"
        }
    }
}

extension Location: UpdatableModelObject {
    public func mergedWith(incrementalChanges: Location) -> Location {
        var result = self
        result.lastTimeUpdated = incrementalChanges.lastTimeUpdated
        // The preferense is given to self values rather than incrementalChanges in this class
        result.coordinates = result.coordinates ?? incrementalChanges.coordinates
        result.imageName = result.imageName ?? incrementalChanges.imageName
        result.countryCode = result.countryCode ?? incrementalChanges.countryCode
        result.countryName = result.countryName ?? incrementalChanges.countryName
        result.region = result.region ?? incrementalChanges.region
        result.cityName = result.cityName ?? incrementalChanges.cityName
        result.nickname = result.nickname ?? incrementalChanges.nickname
        result.zip = result.zip ?? incrementalChanges.zip
        result.fipsCode = result.fipsCode ?? incrementalChanges.fipsCode
        
        result.today = result.today?.mergedWith(incrementalChanges: incrementalChanges.today) ?? incrementalChanges.today
        result.daily = result.daily.mergedWith(incrementalChanges: incrementalChanges.daily)
        result.hourly = result.hourly.mergedWith(incrementalChanges: incrementalChanges.hourly)
        return result
    }
}

extension CLLocationCoordinate2D: Hashable, Equatable {
    private static let comparisonAccuracyLimit: CLLocationDegrees = 0.00001 // approximately 1 meter – more than enough for our purposes. https://en.wikipedia.org/wiki/Decimal_degrees
    
    public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
        // no point in comparing hashes first, I think, since making the hash might be as computationally expensive as this whole thing (I didn't profile it though).
        return fabs(lhs.latitude - rhs.latitude) + fabs(lhs.longitude - rhs.longitude) < CLLocationCoordinate2D.comparisonAccuracyLimit
    }
    
    public func hash(into hasher: inout Hasher) {
        hasher.combine(self.latitude)
        hasher.combine(self.longitude)
    }
}
