//
//  MapRepresentable.swift
//  forShip
//
//  Created by Mamoru Sugita on 2023/10/18.
//

import Foundation
import SwiftUI
import MapboxMaps
import UIKit

struct MapRepresentable: UIViewControllerRepresentable{
    @ObservedObject var ecaData = SharingData.eca
    @ObservedObject var location = SharingData.location
    @ObservedObject var map = SharingData.map
    @ObservedObject var pushHistory = SharingData.pushHistory

    @State var mapVC = MapViewController()
    //作成したいViewControllerを返すメソッド
    func makeUIViewController(context: Context) -> some UIViewController {
        mapVC
    }
    
    //Viewが更新された場合に必要な処理を実装
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        let ecaArea = ecaData.ecaArea.map{ $0.1 }.filter{ $0.isRunning }.first
        if let ecaArea = ecaArea{
            mapVC.updateEcaLine(line: ecaArea.points)
        } else {
            mapVC.removeEcaLine()
        }

        //ECA領域を画面中央に表示
        if let focusEcaAreaId = ecaData.focusEca, let focusEca = ecaData.ecaArea[focusEcaAreaId]{
            mapVC.updateCamera(location: focusEca.centerPosition, zoomlevel: focusEca.zoomLevel)
            mapVC.updateOneTimeEca(eca: focusEca.points)
            //10秒後削除
            DispatchQueue.main.asyncAfter(deadline: .now() + 15.0) {
                mapVC.updateOneTimeEca(eca: nil)
            }
        }

        //通知場所を画面中央に表示
        if let focusPushHistoryId = pushHistory.focusPushHistory, let focusPushHistory = pushHistory.pushHistoryData[focusPushHistoryId]{
            if let position = focusPushHistory.position {
                if let latitude = position.lat, let longitude = position.lon {
                    mapVC.updateCamera(location: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), zoomlevel: nil)
                }
            }
        }

        //自船を画面中央に表示
        if location.focusOwnShip {
            mapVC.updateCamera(location: location.location, zoomlevel: nil)
        }

        if let mylocation = location.location {
            mapVC.updateOwnShip(location: mylocation, bearing: location.heading)

            if let ecaArea = ecaArea{
                mapVC.updateEcaSwitchingLine(center: mylocation, notice: ecaArea.swNotice, start: ecaArea.swStart, finish: ecaArea.swFinish)
            } else {
                mapVC.removeEcaSwitchingLine()
            }
        }
        mapVC.updateWakeLines(legLine: map.legLine, portLine: map.portLine, starboardLine: map.starboardLine)
        mapVC.updateWayPoints(points: map.wayPoints)
    }
}

class MapViewController : UIViewController{
    
    internal var mapView: MapView!
    
    var ownShipSymbol = MapSource(layer: .OwnShip)
    var ecaLine = MapSource(layer: .EcaLine)
    var ecaSwitchingLine = MapSource(layer: .SwitchingLine)
    var ecaSwLineLabel = MapSource(layer: .SwLineLabel)
    var wayPoints = MapSource(layer: .WayPoints)
    var wakeLines = MapSource(layer: .WakeLines)
    var oneTimeEca = MapSource(layer: .OneTimeEca)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let centerCoordinate = CLLocationCoordinate2D(latitude: 37.8, longitude: -96)
//        let url = StyleURI(rawValue: "mapbox://styles/jmarinecloud/cltrwnk5i01j901ra3g4n7dtz")
//        let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 2), styleURI: url)
        let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 2))

        mapView = MapView(frame: view.bounds, mapInitOptions: options)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        self.view.addSubview(mapView)
        mapView.mapboxMap.onNext(event: .mapLoaded) { [self] _ in
            self.addLayers()
        }
    }

    /**
     * 自船アイコンの状態表記
     */
    func addImage(){
        do{
            if let image = UIImage(named: "ownShip_normal"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Normal.rawValue)
            }
            if let image = UIImage(named: "ownShip_alarm"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Alarm.rawValue)
            }
            if let image = UIImage(named: "ownShip_caution"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Caution.rawValue)
            }
            if let image = UIImage(named: "ownShip_emergency"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Emergency.rawValue)
            }
            if let image = UIImage(named: "ownShip_necstemergency"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_NeCST_Emergency.rawValue)
            }
            if let image = UIImage(named: "ownShip_offline"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Offline.rawValue)
            }
            if let image = UIImage(named: "ownShip_unknown"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Unknown.rawValue)
            }
            if let image = UIImage(named: "ownShip_warning"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip_Warning.rawValue)
            }
            if let image = UIImage(named: "swNoticeBack"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.SwNoticeBack.rawValue)
            }
            if let image = UIImage(named: "swStartBack"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.SwStartBack.rawValue)
            }
            if let image = UIImage(named: "swFinishBack"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.SwFinisheBack.rawValue)
            }
        } catch {
            print(debug: "called")
        }
    }
    
    func addLayers(){
        
        addImage()

        //自船
        let point = Point(LocationCoordinate2D(latitude: 0, longitude: 0))
        ownShipSymbol.source.data = .feature(Feature(geometry: point))
        var ownShipSymbolLayer = SymbolLayer(id: ownShipSymbol.layerId)
        ownShipSymbolLayer.source = ownShipSymbol.sourceId
        ownShipSymbolLayer.iconImage = .expression(Exp(.get) {
            PropertyKey.IconImage.rawValue
        })
        ownShipSymbolLayer.iconRotationAlignment = .constant(.map)
        ownShipSymbolLayer.iconAllowOverlap = .constant(true)
        ownShipSymbolLayer.iconIgnorePlacement = .constant(true)
        ownShipSymbolLayer.iconRotate = .expression(Exp(.get) {
            PropertyKey.Bearing.rawValue
        })

        try! mapView.mapboxMap.style.addSource(ownShipSymbol.source, id: ownShipSymbol.sourceId)
        try? mapView.mapboxMap.style.addLayer(ownShipSymbolLayer)

        //ECA
        let ecaLineString = LineString([LocationCoordinate2D(latitude: 0, longitude: 0)])
        let ecaLinefeature = Feature(geometry: ecaLineString)
        ecaLine.source.data = .feature(ecaLinefeature)
        var ecaLineLayer = LineLayer(id: ecaLine.layerId)
        ecaLineLayer.source = ecaLine.sourceId
        ecaLineLayer.lineColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })
        ecaLineLayer.lineDasharray = .constant([2,1])

        try! mapView.mapboxMap.style.addSource(ecaLine.source, id: ecaLine.sourceId)
        try? mapView.mapboxMap.style.addLayer(ecaLineLayer)
        
        //
        _ = LineString([LocationCoordinate2D(latitude: 0, longitude: 0)])
        let oneTimeEcaLinefeature = Feature(geometry: ecaLineString)
        oneTimeEca.source.data = .feature(oneTimeEcaLinefeature)
        var oneTimeEcaLayer = LineLayer(id: oneTimeEca.layerId)
        oneTimeEcaLayer.source = oneTimeEca.sourceId
        oneTimeEcaLayer.lineColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })
        oneTimeEcaLayer.lineDasharray = .constant([2,1])

        try! mapView.mapboxMap.style.addSource(oneTimeEca.source, id: oneTimeEca.sourceId)
        try? mapView.mapboxMap.style.addLayer(oneTimeEcaLayer)
        
        //
        ecaSwitchingLine.source.data = .featureCollection(FeatureCollection(features: []))
        var ecaSwitchingLineLayer = LineLayer(id: ecaSwitchingLine.layerId)
        ecaSwitchingLineLayer.source = ecaSwitchingLine.sourceId
        ecaSwitchingLineLayer.lineColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })
        ecaSwitchingLineLayer.lineDasharray = .constant([2,1])

        try! mapView.mapboxMap.style.addSource(ecaSwitchingLine.source, id: ecaSwitchingLine.sourceId)
        try? mapView.mapboxMap.style.addLayer(ecaSwitchingLineLayer)
        
        //ECA ラベル
        ecaSwLineLabel.source.data = .featureCollection(FeatureCollection(features: []))
        var ecaSwLabelLayer = SymbolLayer(id: ecaSwLineLabel.layerId)
        ecaSwLabelLayer.source = ecaSwLineLabel.sourceId
        ecaSwLabelLayer.textField = .expression(Exp(.get) {
            PropertyKey.Text.rawValue
        })
        ecaSwLabelLayer.iconImage = .expression(Exp(.get) {
            PropertyKey.IconImage.rawValue
        })
        ecaSwLabelLayer.iconColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })
        ecaSwLabelLayer.iconTextFit = .constant(.both)
        ecaSwLabelLayer.iconTextFitPadding = .constant([0,6,0,6])

        try! mapView.mapboxMap.style.addSource(ecaSwLineLabel.source, id: ecaSwLineLabel.sourceId)
        try? mapView.mapboxMap.style.addLayer(ecaSwLabelLayer)
        
        //航路
        wakeLines.source.data = .featureCollection(FeatureCollection(features: []))
        var wakeLineLayer = LineLayer(id: wakeLines.layerId)
        wakeLineLayer.source = wakeLines.sourceId
        wakeLineLayer.lineColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })

        try! mapView.mapboxMap.style.addSource(wakeLines.source, id: wakeLines.sourceId)
        try? mapView.mapboxMap.style.addLayer(wakeLineLayer)
        
        //WayPoint
        wayPoints.source.data = .featureCollection(FeatureCollection(features: []))
        var wayPointsLayer = SymbolLayer(id: wayPoints.layerId)
        wayPointsLayer.source = wayPoints.sourceId
        wayPointsLayer.iconImage = .expression(Exp(.get) {
            PropertyKey.IconImage.rawValue
        })
        wayPointsLayer.iconColor = .expression(Exp(.get) {
            PropertyKey.Color.rawValue
        })

        try! mapView.mapboxMap.style.addSource(wayPoints.source, id: wayPoints.sourceId)
        try? mapView.mapboxMap.style.addLayer(wayPointsLayer)
    }
    
    ///自船
    func updateOwnShip(location: CLLocationCoordinate2D, bearing: Double){
        do {
            var shipIcon = IconImage.OwnShip_Normal.rawValue

            switch SharingData.location.status {
            case ShipAlert.Offline:
                shipIcon = IconImage.OwnShip_Offline.rawValue
            case ShipAlert.Unknown:
                shipIcon = IconImage.OwnShip_Unknown.rawValue
            case ShipAlert.Normal:
                shipIcon = IconImage.OwnShip_Normal.rawValue
            case ShipAlert.Caution:
                shipIcon = IconImage.OwnShip_Caution.rawValue
            case ShipAlert.Warrning:
                shipIcon = IconImage.OwnShip_Warning.rawValue
            case ShipAlert.Alarm:
                shipIcon = IconImage.OwnShip_Alarm.rawValue
            case ShipAlert.Emergency:
                shipIcon = IconImage.OwnShip_Emergency.rawValue
            case ShipAlert.NecstEmergency:
                shipIcon = IconImage.OwnShip_NeCST_Emergency.rawValue
            }

            var geoJSON = Feature(geometry: Point(location))
            geoJSON.properties = [PropertyKey.Bearing.rawValue: .number(bearing),
                                  PropertyKey.IconImage.rawValue: .string(shipIcon)]
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: self.ownShipSymbol.sourceId, geoJSON: .feature(geoJSON))
        } catch {
            print(debug: "called")
        }
    }
    
    ///カメラ
    func updateCamera(location: CLLocationCoordinate2D?, zoomlevel: CGFloat?){
        if let level = zoomlevel {
            self.mapView.camera.ease(to: CameraOptions(center: location, zoom: level), duration: 1)
        } else {
            self.mapView.camera.ease(to: CameraOptions(center: location), duration: 1)
        }
        if SharingData.eca.focusEca != nil {
            SharingData.eca.focusEca = nil
        }
        if SharingData.pushHistory.focusPushHistory != nil {
            SharingData.pushHistory.focusPushHistory = nil
        }
        if SharingData.location.focusOwnShip {
            SharingData.location.focusOwnShip = false
        }
    }

    ///Ecaの線3本
    func updateEcaSwitchingLine(center: CLLocationCoordinate2D, notice: Float, start: Float, finish: Float){
        do{
            var switchingLines : [Feature] = []
            var switchingLabels : [Feature] = []
            
            let circlePoints_notice = getCirclePoints(center: center, radiusKm: LocationCalculation.nm2km(nm: Double(notice)))
            var noticeLine = Feature(geometry: LineString(circlePoints_notice))
            noticeLine.properties = [PropertyKey.Color.rawValue: .string("#62AB28")]
            switchingLines.append(noticeLine)
            if let point = circlePoints_notice.first{
                var label = Feature(geometry: Point(point))
                label.properties = [PropertyKey.Text.rawValue: .string("Advance Notice"),
                                          PropertyKey.IconImage.rawValue: .string(IconImage.SwNoticeBack.rawValue),
                                          PropertyKey.Color.rawValue: .string("#62AB28")]
                switchingLabels.append(label)
            }
            
            
            let circlePoints_start = getCirclePoints(center: center, radiusKm: LocationCalculation.nm2km(nm: Double(start)))
            var startLine = Feature(geometry: LineString(circlePoints_start))
            startLine.properties = [PropertyKey.Color.rawValue: .string("#ECD932")]
            switchingLines.append(startLine)
            if let point = circlePoints_start.first{
                var label = Feature(geometry: Point(point))
                label.properties = [PropertyKey.Text.rawValue: .string("Switching Start"),
                                          PropertyKey.IconImage.rawValue: .string(IconImage.SwStartBack.rawValue),
                                          PropertyKey.Color.rawValue: .string("#ECD932")]
                switchingLabels.append(label)
            }
            
            let circlePoints_finish = getCirclePoints(center: center, radiusKm: LocationCalculation.nm2km(nm: Double(finish)))
            var finishLine = Feature(geometry: LineString(circlePoints_finish))
            finishLine.properties = [PropertyKey.Color.rawValue: .string("#EF6135")]
            switchingLines.append(finishLine)
            if let point = circlePoints_finish.first{
                var label = Feature(geometry: Point(point))
                label.properties = [PropertyKey.Text.rawValue: .string("Switching Finish"),
                                          PropertyKey.IconImage.rawValue: .string(IconImage.SwFinisheBack.rawValue),
                                          PropertyKey.Color.rawValue: .string("#EF6135")]
                switchingLabels.append(label)
            }
            
            let lineGeoJson = FeatureCollection(features: switchingLines)
            let labelGeoJson = FeatureCollection(features: switchingLabels)
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaSwitchingLine.sourceId, geoJSON: .featureCollection(lineGeoJson))
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaSwLineLabel.sourceId, geoJSON: .featureCollection(labelGeoJson))
        }catch{}
    }

    func removeEcaSwitchingLine(){
        do{
            let switchingLines : [Feature] = []
            let switchingLabels : [Feature] = []

            let lineGeoJson = FeatureCollection(features: switchingLines)
            let labelGeoJson = FeatureCollection(features: switchingLabels)
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaSwitchingLine.sourceId, geoJSON: .featureCollection(lineGeoJson))
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaSwLineLabel.sourceId, geoJSON: .featureCollection(labelGeoJson))
        }catch{}
    }

    ///円の位置情報を返す
    private func getCirclePoints(center: CLLocationCoordinate2D, radiusKm: Double) -> [CLLocationCoordinate2D]{
        var circlePoints : [CLLocationCoordinate2D] = []
        for deg in stride(from: 0, through: 360, by: 6) {
            let point = LocationCalculation.findPointAtDistanceFrom(
                startPointLat: center.latitude,
                startPointLon: center.longitude,
                initialBearing: Double(deg),
                distanceKilometers: radiusKm
            )
            circlePoints.append(point)
        }
        return circlePoints
    }

    func updateEcaLine(line: [CLLocationCoordinate2D]){
        do{
            var geoJson = Feature(geometry: LineString(line))
            geoJson.properties = [PropertyKey.Color.rawValue: .string(Color.blue.description)]
            
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaLine.sourceId, geoJSON: .feature(geoJson))
        }catch{}
    }

    func removeEcaLine(){
        do{
            let line: [CLLocationCoordinate2D] = []
            let geoJson = Feature(geometry: LineString(line))
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaLine.sourceId, geoJSON: .feature(geoJson))
        }catch{}
    }
    
    ///一時的に見たいEcaを表示
    func updateOneTimeEca(eca: [CLLocationCoordinate2D]?){
        do{
            let eca = eca ?? []
            var geoJson = Feature(geometry: LineString(eca))
            geoJson.properties = [PropertyKey.Color.rawValue: .string(Color.black.description)]
            
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: oneTimeEca.sourceId, geoJSON: .feature(geoJson))
        }catch{
            
        }
    }

    func updateWakeLines(legLine: [CLLocationCoordinate2D], portLine: [CLLocationCoordinate2D], starboardLine: [CLLocationCoordinate2D]){
        do{
            var legLineFeature = Feature(geometry: LineString(legLine))
            legLineFeature.properties = [PropertyKey.Color.rawValue: .string(Color.blue.description)]
            
            var portLineFeature = Feature(geometry: LineString(portLine))
            portLineFeature.properties = [PropertyKey.Color.rawValue: .string(Color.blue.description)]
            
            var starboardLineFeature = Feature(geometry: LineString(starboardLine))
            starboardLineFeature.properties = [PropertyKey.Color.rawValue: .string(Color.blue.description)]
            
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: wakeLines.sourceId, geoJSON: .featureCollection(FeatureCollection(features: [legLineFeature, portLineFeature, starboardLineFeature])))
        }catch{}
    }
    
    ///子午線を跨ぐ位置情報をチェックして東経ベースに補正する。
    static func checkStraddleMeridian(_ locations: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D]{
        let eastLon = FloatingPointSign.plus
        var returnLocations : [CLLocationCoordinate2D] = []
        for location in locations {
            if location.longitude.sign != eastLon{
                var loc = location
                loc.longitude += 360
                returnLocations.append(loc)
            }else{
                returnLocations.append(location)
            }
        }
        
        return returnLocations
    }
    
    func updateWayPoints(points: [CLLocationCoordinate2D]){
        do{
            var wayPointsFeatures : [Feature] = []
            
            for point in points{
                var feature = Feature(geometry: Point(point))
                feature.properties = [PropertyKey.IconImage.rawValue: .string(IconImage.SwFinisheBack.rawValue),
                                          PropertyKey.Color.rawValue: .string("#EF6135")]
                wayPointsFeatures.append(feature)
            }
            
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: ecaSwLineLabel.sourceId, geoJSON: .featureCollection(FeatureCollection(features: wayPointsFeatures)))
        }catch{}
    }

    enum PropertyKey : String {
        case IconImage
        case Bearing
        case Color
        case Text
    }
    
    enum IconImage: String, CaseIterable {
        case OwnShip_Alarm
        case OwnShip_Caution
        case OwnShip_Emergency
        case OwnShip_NeCST_Emergency
        case OwnShip_Normal
        case OwnShip_Offline
        case OwnShip_Unknown
        case OwnShip_Warning
        case SwNoticeBack
        case SwStartBack
        case SwFinisheBack
    }
}

