import UIKit
import MapKit
import RxSwift
import Realm
import RealmSwift
import PKHUD
import MobileCoreServices
import ExceptionCatcher

class EventPin: NSObject, MKAnnotation {
    var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?
    
    init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String) {
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
    }
    
    convenience init(event: VehicleEvent) {
        let coordinate = CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude)
        let address = event.address ?? "\(event.latitude), \(event.longitude)"
        let date = Date(timeIntervalSince1970: event.date)
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        let dateStr = formatter.string(from: date)
        
        if let number = event.number {
            self.init(coordinate: coordinate, title: number, subtitle: dateStr)
        } else {
            self.init(coordinate: coordinate, title: dateStr, subtitle: address)
        }
    }
}

enum EventsMode {
    case map
    case list
}

class EventsController: UIViewController, UITableViewDataSource, UITableViewDelegate, MKMapViewDelegate {

    @IBOutlet weak var map: MKMapView!
    @IBOutlet weak var tableView: UITableView!
    
    let bag = DisposeBag()
    var modeButton: UIBarButtonItem!
    var addButton: UIBarButtonItem!
    var pasteButton: UIBarButtonItem!
    var mode: EventsMode = .map
    
    public var vehicle: Vehicle? {
        didSet {
            if self.isViewLoaded {
                self.updateInterface()
            }
        }
    }
    
    private var pins: [EventPin] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = self.vehicle?.getNumber() ?? NSLocalizedString("Events", comment: "")

        #if targetEnvironment(macCatalyst)
        
        if #available(OSX 11.0, *) {
            self.map.showsCompass = true
        }
        self.map.showsZoomControls = true
        
        #endif
        
        self.modeButton = UIBarButtonItem(image: UIImage(systemName: "list.bullet"), style: .plain, target: self, action: #selector(switchMode(_:)))
        self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addEvent(_:)))
        self.pasteButton = UIBarButtonItem(image: UIImage(systemName: "doc.on.clipboard"), style: .plain, target: self, action: #selector(pasteEvent(_:)))
        self.setupBarButtonItems()
        self.map.delegate = self
        
        self.updateInterface()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.setupBarButtonItems()
    }
    
    func updateInterface() {
        if let vehicle = self.vehicle {
            self.pins = vehicle.events.map(EventPin.init(event:))
        }
        
        if !self.pins.isEmpty {
            self.map.removeAnnotations(self.map.annotations)
            self.map.addAnnotations(self.pins)
            self.map.centerOnPins()
        } else {
            self.map.showsUserLocation = true
        }
        
        self.tableView.reloadData()
    }
    
    @objc func switchMode(_ sender: UIBarButtonItem) {
        switch self.mode {
        case .map:
            self.mode = .list
            self.tableView.reloadData()
            self.tableView.isHidden = false
            self.map.isHidden = true
            self.modeButton.image = UIImage(systemName: "map")
        case .list:
            self.mode = .map
            self.tableView.isHidden = true
            self.map.isHidden = false
            self.modeButton.image = UIImage(systemName: "list.bullet")
        }
    }
    
    // MARK: - UITableViewDataSource
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.vehicle?.events.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "EventCell", for: indexPath) as? EventCell else {
            return UITableViewCell()
        }
        
        if let event = self.vehicle?.events[indexPath.row] {
            cell.configure(with: event)
        }

        return cell
    }
    
    // MARK: - UITableViewDelegate
    
    func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            let copy = UIAction(title: NSLocalizedString("Copy", comment: ""), image: UIImage(systemName: "doc.on.doc")) { action in
                self.copyEvent(index: indexPath.row)
            }
            
            let share = UIAction(title: NSLocalizedString("Share", comment: ""), image: UIImage(systemName: "square.and.arrow.up")) { action in
                self.shareEvent(index: indexPath.row)
            }
            
            let edit = UIAction(title: NSLocalizedString("Edit", comment: ""), image: UIImage(systemName: "pencil")) { action in
                self.editEvent(index: indexPath.row)
            }
            
            let delete = UIAction(title: NSLocalizedString("Delete", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { action in
                self.deleteEvent(index: indexPath.row)
            }

            return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [copy, share, edit, delete])
        }
    }
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let copy = UIContextualAction(style: .normal, title: NSLocalizedString("Copy", comment: "")) { action, view, completion in
            self.copyEvent(index: indexPath.row)
            completion(true)
        }
        copy.image = UIImage(systemName: "doc.on.doc")
        copy.backgroundColor = .systemBlue
        
        let delete = UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { action, view, completion in
            self.deleteEvent(index: indexPath.row, completion: completion)
        }
        delete.image = UIImage(systemName: "trash")
        
        let edit = UIContextualAction(style: .normal, title: NSLocalizedString("Edit", comment: "")) { action, view, completion in
            self.editEvent(index: indexPath.row)
            completion(true)
        }
        edit.image = UIImage(systemName: "pencil")
        edit.backgroundColor = .systemBlue
        
        let configuration = UISwipeActionsConfiguration(actions: [delete, edit, copy])
        configuration.performsFirstActionWithFullSwipe = false
        return configuration
    }
    
    // MARK: - Event actions
    
    func deleteEvent(index: Int, completion: ((Bool) -> Void)? = nil) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        
        let event = vehicle.events[index]
        if let eventId = event.id {
            HUD.show(.progress)
            Api.remove(event: eventId).observeOn(MainScheduler.instance).subscribe(onSuccess: { vehicle in
                let result = self.update(vehicle: vehicle)
                completion?(result)
            }, onError: { error in
                completion?(false)
                HUD.show(error: error)
                print(error)
            }).disposed(by: self.bag)
        } else {
            self.showAlert(title: "Error", message: "Event ID is not found. Please try to update vehicle record, containing this event.")
        }
    }
    
    func editEvent(index: Int) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        
        let event = vehicle.events[index]
        let sb = UIStoryboard(name: "Main", bundle: nil)
        let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController
        controller.title = NSLocalizedString("Edit event", comment: "")
        controller.date = Date(timeIntervalSince1970: event.date)
        controller.placemark =  Placemark(latitude: event.latitude, longitude: event.longitude, address: event.address)
        controller.onDone = { newEvent in
            newEvent.id = event.id
            self.navigationController?.popViewController(animated: true, completion: {
                HUD.show(.progress)
                Api.edit(event: newEvent)
                    .observeOn(MainScheduler.instance)
                    .subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
                    { error in
                        HUD.show(error: error)
                    })
                    .disposed(by: self.bag)
            })
        }
        self.navigationController?.pushViewController(controller, animated: true)
    }
    
    func copyEvent(index: Int) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        
        var items: [String: Any] = [:]
        let event = vehicle.events[index]
        
        if let url = event.getMapLink() {
            items[kUTTypeURL as String] = url
            //UIPasteboard.general.setValue(url, forPasteboardType: kUTTypeURL as String)
        }
        
        if let data = try? JSONEncoder().encode(event) {
            items["pro.aliencat.vehicle.event"] = data
            //UIPasteboard.general.setData(data, forPasteboardType: "pro.aliencat.vehicle.event")
        }
        
        if !items.isEmpty {
            UIPasteboard.general.items = [items]
        }
        
        self.setupBarButtonItems()
    }
    
    func shareEvent(index: Int) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        
        let event = vehicle.events[index]
        if let url = event.getMapLink() {
            let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
            self.present(controller, animated: true)
        }
    }
    
    @objc func addEvent(_ sender: UIBarButtonItem) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        
        let sb = UIStoryboard(name: "Main", bundle: nil)
        let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController
        controller.title = NSLocalizedString("Add new event", comment: "")
        controller.onDone = { newEvent in
            self.navigationController?.popViewController(animated: true, completion: {
                HUD.show(.progress)
                Api.add(event: newEvent, to: vehicle.getNumber())
                    .observeOn(MainScheduler.instance)
                    .subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
                    { error in
                        HUD.show(error: error)
                    })
                    .disposed(by: self.bag)
            })
        }
        self.navigationController?.pushViewController(controller, animated: true)
    }
    
    @objc func pasteEvent(_ sender: UIBarButtonItem) {
        guard let vehicle = self.vehicle else {
            HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
            return
        }
        guard let data = UIPasteboard.general.data(forPasteboardType: "pro.aliencat.vehicle.event") else { return }
        
        do {
            let event = try JSONDecoder().decode(VehicleEvent.self, from: data)
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .medium
            let msg = formatter.string(from: Date(timeIntervalSince1970: event.date)) + "\n" + event.getLocationString()
            
            let alert = UIAlertController(title: NSLocalizedString("Paste event", comment: "from clipboard"), message: msg, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: NSLocalizedString("Paste", comment: "from clipboard"), style: .default, handler: { action in
                HUD.show(.progress)
                event.id = UUID().uuidString
                Api.add(event: event, to: vehicle.getNumber())
                    .observeOn(MainScheduler.instance)
                    .subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
                    { error in
                        HUD.show(error: error)
                    })
                    .disposed(by: self.bag)
            }))
            alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
            self.present(alert, animated: true)
        } catch {
            print(error)
        }
    }
    
    func setupBarButtonItems() {
        let pasteVisible = UIPasteboard.general.data(forPasteboardType: "pro.aliencat.vehicle.event") != nil
        self.navigationItem.rightBarButtonItems = pasteVisible ? [self.modeButton, self.addButton, self.pasteButton] : [self.modeButton, self.addButton]
    }
    
    @discardableResult
    func update(vehicle: Vehicle) -> Bool {
        do {
            if let realm = self.vehicle?.realm {
                try ExceptionCatcher.catch {
                    try realm.write {
                        realm.add(vehicle, update: .all)
                    }
                }
            } else {
                self.vehicle?.events.removeAll()
                self.vehicle?.events.append(objectsIn: vehicle.events)
            }
            self.updateInterface()
            HUD.hide()
            return true
        } catch {
            HUD.hide()
            self.show(error: error)
            print(error)
            return false
        }
    }
    
    // MARK: - MKMapViewDelegate
    
    public func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        let region = MKCoordinateRegion(center: userLocation.coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
        self.map.setRegion(region, animated: true)
        self.map.showsUserLocation = false
    }
}
