//
//  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 my = SharingData.my
    
    @State var mapVC = MapViewController()
    func makeUIViewController(context: Context) -> some UIViewController {
        mapVC
    }
    
    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)
        }
        
        if let mylocation = my.location {
            mapVC.updateOwnShip(location: mylocation, bearing: my.heading)
            
            if let ecaArea = ecaArea{
                mapVC.updateEcaSwitchingLine(center: mylocation, notice: ecaArea.swNotice, start: ecaArea.swStart, finish: ecaArea.swFinish)
            }
        }
    }
}

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)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let centerCoordinate = CLLocationCoordinate2D(latitude: 37.8, longitude: -96)
        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"){
                try mapView.mapboxMap.style.addImage(image, id: IconImage.OwnShip.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{}
    }
    
    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)
        
        
        let ecaLineString = LineString([LocationCoordinate2D(latitude: 0, longitude: 0)])
        var 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)
        
        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)
        
        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)
    }
    
    func updateOwnShip(location: CLLocationCoordinate2D, bearing: Double){
        do {
            var geoJSON = Feature(geometry: Point(location))
            geoJSON.properties = [PropertyKey.Bearing.rawValue: .number(bearing),
                                  PropertyKey.IconImage.rawValue: .string(IconImage.OwnShip.rawValue)]
            try self.mapView.mapboxMap.style.updateGeoJSONSource(withId: self.ownShipSymbol.sourceId, geoJSON: .feature(geoJSON))
        }catch{}
    }
    
    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)
            }
            
            var lineGeoJson = FeatureCollection(features: switchingLines)
            var 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{}
    }

    
    
    
    enum PropertyKey : String {
        case IconImage
        case Bearing
        case Color
        case Text
    }
    
    enum IconImage: String, CaseIterable{
        case OwnShip
        case SwNoticeBack
        case SwStartBack
        case SwFinisheBack
    }
}

