import Foundation
import BaiduMapAPI_Base
import BaiduMapAPI_Map
import BaiduMapAPI_Search
import BaiduMapAPI_Utils

@objc public class RouteStepDTO: NSObject {
    @objc public var instruction: String = ""
    @objc public var distance: Int = 0
    @objc public var duration: Int = 0
    @objc public var lats: [NSNumber] = []
    @objc public var lngs: [NSNumber] = []
}

@objc public class RouteLineDTO: NSObject {
    @objc public var distance: Int = 0
    @objc public var duration: Int = 0
    @objc public var steps: [RouteStepDTO] = []
}

@objc public class RouteResultDTO: NSObject {
    @objc public var routeLines: [RouteLineDTO] = []
}

fileprivate func bmkTimeToSeconds(_ t: BMKTime) -> Int {
    return Int(t.hours) * 3600 + Int(t.minutes) * 60 + Int(t.seconds)
}

fileprivate func parseStepPoints(_ points: UnsafeMutablePointer<BMKMapPoint>?, count: Int32) -> (lats: [NSNumber], lngs: [NSNumber]) {
    var lats: [NSNumber] = []
    var lngs: [NSNumber] = []
    guard let pts = points, count > 0 else {
        return (lats, lngs)
    }
    for i in 0..<Int(count) {
        let coord = BMKCoordinateForMapPoint(pts[i])
        lats.append(NSNumber(value: coord.latitude))
        lngs.append(NSNumber(value: coord.longitude))
    }
    return (lats, lngs)
}

fileprivate func buildWalkingDTO(_ result: BMKWalkingRouteResult?) -> RouteResultDTO {
    let dto = RouteResultDTO()
    guard let routes = result?.routes as? [BMKWalkingRouteLine] else { return dto }
    for line in routes {
        let lineDTO = RouteLineDTO()
        lineDTO.distance = Int(line.distance)
        lineDTO.duration = bmkTimeToSeconds(line.duration)
        if let steps = line.steps as? [BMKWalkingStep] {
            for step in steps {
                let stepDTO = RouteStepDTO()
                stepDTO.instruction = step.entraceInstruction ?? ""
                stepDTO.distance = Int(step.distance)
                stepDTO.duration = Int(step.duration)
                let pts = parseStepPoints(step.points, count: step.pointsCount)
                stepDTO.lats = pts.lats
                stepDTO.lngs = pts.lngs
                lineDTO.steps.append(stepDTO)
            }
        }
        dto.routeLines.append(lineDTO)
    }
    return dto
}

fileprivate func buildRidingDTO(_ result: BMKRidingRouteResult?) -> RouteResultDTO {
    let dto = RouteResultDTO()
    guard let routes = result?.routes as? [BMKRidingRouteLine] else { return dto }
    for line in routes {
        let lineDTO = RouteLineDTO()
        lineDTO.distance = Int(line.distance)
        lineDTO.duration = bmkTimeToSeconds(line.duration)
        if let steps = line.steps as? [BMKRidingStep] {
            for step in steps {
                let stepDTO = RouteStepDTO()
                stepDTO.instruction = step.entraceInstruction ?? ""
                stepDTO.distance = Int(step.distance)
                stepDTO.duration = Int(step.duration)
                let pts = parseStepPoints(step.points, count: step.pointsCount)
                stepDTO.lats = pts.lats
                stepDTO.lngs = pts.lngs
                lineDTO.steps.append(stepDTO)
            }
        }
        dto.routeLines.append(lineDTO)
    }
    return dto
}

fileprivate func buildDrivingDTO(_ result: BMKDrivingRouteResult?) -> RouteResultDTO {
    let dto = RouteResultDTO()
    guard let routes = result?.routes as? [BMKDrivingRouteLine] else { return dto }
    for line in routes {
        let lineDTO = RouteLineDTO()
        lineDTO.distance = Int(line.distance)
        lineDTO.duration = bmkTimeToSeconds(line.duration)
        if let steps = line.steps as? [BMKDrivingStep] {
            for step in steps {
                let stepDTO = RouteStepDTO()
                let inst = step.instruction ?? ""
                stepDTO.instruction = inst.isEmpty ? (step.entraceInstruction ?? "") : inst
                stepDTO.distance = Int(step.distance)
                stepDTO.duration = Int(step.duration)
                let pts = parseStepPoints(step.points, count: step.pointsCount)
                stepDTO.lats = pts.lats
                stepDTO.lngs = pts.lngs
                lineDTO.steps.append(stepDTO)
            }
        }
        dto.routeLines.append(lineDTO)
    }
    return dto
}

@objc public class RouteSearchDelegateImpl: NSObject, BMKRouteSearchDelegate {
    @objc public var onWalking: ((RouteResultDTO?, NSNumber) -> Void)?
    @objc public var onRiding: ((RouteResultDTO?, NSNumber) -> Void)?
    @objc public var onDriving: ((RouteResultDTO?, NSNumber) -> Void)?

    public func onGetWalkingRouteResult(_ searcher: BMKRouteSearch, result: BMKWalkingRouteResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetWalkingRouteResult invoked, errorCode=%d", errorCode.rawValue)
        let dto = errorCode == BMK_SEARCH_NO_ERROR ? buildWalkingDTO(result) : nil
        onWalking?(dto, NSNumber(value: errorCode.rawValue))
    }

    public func onGetRidingRouteResult(_ searcher: BMKRouteSearch, result: BMKRidingRouteResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetRidingRouteResult invoked, errorCode=%d", errorCode.rawValue)
        let dto = errorCode == BMK_SEARCH_NO_ERROR ? buildRidingDTO(result) : nil
        onRiding?(dto, NSNumber(value: errorCode.rawValue))
    }

    public func onGetDrivingRouteResult(_ searcher: BMKRouteSearch, result: BMKDrivingRouteResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetDrivingRouteResult invoked, errorCode=%d", errorCode.rawValue)
        let dto = errorCode == BMK_SEARCH_NO_ERROR ? buildDrivingDTO(result) : nil
        onDriving?(dto, NSNumber(value: errorCode.rawValue))
    }
}

@objc public class PoiSearchDelegateImpl: NSObject, BMKPoiSearchDelegate {
    @objc public var onResult: ((BMKPoiSearch, BMKPOISearchResult?, NSNumber) -> Void)?

    public func onGetPoiResult(_ searcher: BMKPoiSearch, result poiResult: BMKPOISearchResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetPoiResult invoked, errorCode=%d", errorCode.rawValue)
        onResult?(searcher, poiResult, NSNumber(value: errorCode.rawValue))
    }
}

@objc public class GeoCodeSearchDelegateImpl: NSObject, BMKGeoCodeSearchDelegate {
    @objc public var onGeoCode: ((BMKGeoCodeSearch, BMKGeoCodeSearchResult?, NSNumber) -> Void)?
    @objc public var onReverseGeoCode: ((BMKGeoCodeSearch, BMKReverseGeoCodeSearchResult?, NSNumber) -> Void)?

    public func onGetGeoCodeResult(_ searcher: BMKGeoCodeSearch, result: BMKGeoCodeSearchResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetGeoCodeResult invoked, errorCode=%d", errorCode.rawValue)
        onGeoCode?(searcher, result, NSNumber(value: errorCode.rawValue))
    }

    public func onGetReverseGeoCodeResult(_ searcher: BMKGeoCodeSearch, result: BMKReverseGeoCodeSearchResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetReverseGeoCodeResult invoked, errorCode=%d", errorCode.rawValue)
        onReverseGeoCode?(searcher, result, NSNumber(value: errorCode.rawValue))
    }
}

@objc public class SuggestionSearchDelegateImpl: NSObject, BMKSuggestionSearchDelegate {
    @objc public var onResult: ((BMKSuggestionSearch, BMKSuggestionSearchResult?, NSNumber) -> Void)?

    public func onGetSuggestionResult(_ searcher: BMKSuggestionSearch, result: BMKSuggestionSearchResult?, errorCode: BMKSearchErrorCode) {
        NSLog("[SearchDelegates] onGetSuggestionResult invoked, errorCode=%d", errorCode.rawValue)
        onResult?(searcher, result, NSNumber(value: errorCode.rawValue))
    }
}
