//
// ShareViewController.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
import Social
import Shared
import TigaseSwift
import MobileCoreServices

class ShareViewController: SLComposeServiceViewController {
    
    var account: String? = nil;
    var recipients: [JID] = [];
    
    weak var handler: EventHandler?;
    
    lazy var xmppClient: XMPPClient = {
        let client = XMPPClient();
        let sslHandler: ((SessionObject, SecTrust)->Bool) = {(sessionObject,secTrust) -> Bool in
            return true;
        };
        client.sessionObject.setProperty(SocketConnector.SSL_CERTIFICATE_VALIDATOR, value: sslHandler);
        _ = client.modulesManager.register(AuthModule());
        _ = client.modulesManager.register(StreamFeaturesModule());
        _ = client.modulesManager.register(SaslModule());
        _ = client.modulesManager.register(ResourceBinderModule());
        _ = client.modulesManager.register(SessionEstablishmentModule());
        _ = client.modulesManager.register(DiscoveryModule());
        client.modulesManager.register(PresenceModule()).initialPresence = false;
        let messageModule = client.modulesManager.register(MessageModule());
        let rosterModule =  client.modulesManager.register(RosterModule());
        _ = client.modulesManager.register(HttpFileUploadModule());
        
        let handler = ShareEventHandler();
        handler.controller = self;
        self.handler = handler;
        
        client.eventBus.register(handler: handler, for: RosterModule.ItemUpdatedEvent.TYPE)
        return client;
    }();
    
    lazy var accountConfigurationItem: SLComposeSheetConfigurationItem = {
        let item = SLComposeSheetConfigurationItem()!;
        item.title = "Account";
        item.tapHandler = self.showAccountSelection;
        return item;
    }();
    
    lazy var buddiesConfigurationItem: SLComposeSheetConfigurationItem = {
        let item = SLComposeSheetConfigurationItem()!;
        item.title = "Recipients";
        item.tapHandler = self.showRecipientsSelection;
        return item;
    }();

    weak var rosterController: RecipientsSelectionViewController?;
    
    var webUrl: URL?;
    
    var sharedDefaults = UserDefaults(suiteName: "group.snikket.share");
    
    override func isContentValid() -> Bool {
        return account != nil && xmppClient.state == .connected && recipients.count > 0;
    }
    
    override func viewDidLoad() {
        super.viewDidLoad();
        let dbURL = DBConnection.mainDbURL();
        if !FileManager.default.fileExists(atPath: dbURL.path) {
            let controller = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert);
            controller.addAction(UIAlertAction(title: "OK", style: .destructive, handler: { (action) in
                self.extensionContext?.cancelRequest(withError: ShareError.firstRun);
            }))
            self.present(controller, animated: true, completion: nil);
        }
    }
    
    override func presentationAnimationDidFinish() {
        if !sharedDefaults!.bool(forKey: "SharingViaHttpUpload") {
            var error = true;
            if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
                error = !provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String);
            }
            if error {
                self.showAlert(title: "Failure", message: "Sharing feature with HTTP upload is disabled within application. To use this feature you need to enable sharing with HTTP upload in application");
            }
        }
    }
    
    override func didSelectPost() {
        if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
            if provider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
                provider.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil, completionHandler: { (item, error) in
                    if let localUrl = item as? URL {
                        let uti = try? localUrl.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier;
                        let mimeType = uti != nil ? (UTTypeCopyPreferredTagWithClass(uti! as CFString, kUTTagClassMIMEType)?.takeRetainedValue() as String?) : nil;
                        let size = try? FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as? UInt64;
                        self.upload(localUrl: localUrl, type: mimeType, handler: {(remoteUrl) in
                            guard remoteUrl != nil else {
                                self.showAlert(title: "Failure", message: "Please try again later.");
                                return;
                            }
                            
                            if self.sharedDefaults!.integer(forKey: "fileDownloadSizeLimit") > 0 {
                                let hash = Digest.sha1.digest(toHex: remoteUrl!.absoluteString.data(using: .utf8)!)!;
                            
                                var params: [String: Any] = [
                                    "jids": self.recipients.map({ $0.bareJid.stringValue }),
                                    "name": localUrl.lastPathComponent,
                                    "timestamp": Date()
                                ];
                                if mimeType != nil {
                                    params["mimeType"] = mimeType;
                                }
                                if size != nil {
                                    params["size"] = Int(size!);
                                }
                            
                                let localUploadDirUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.snikket.shared")!.appendingPathComponent("upload", isDirectory: true);
                                if !FileManager.default.fileExists(atPath: localUploadDirUrl.path) {
                                    try? FileManager.default.createDirectory(at: localUploadDirUrl, withIntermediateDirectories: true, attributes: nil);
                                }
                                do {
                                    try FileManager.default.copyItem(at: localUrl, to: localUploadDirUrl.appendingPathComponent(hash, isDirectory: false));
                                    self.sharedDefaults!.set(params as Any?, forKey: "upload-\(hash)");
                                } catch {
                                    print("could not copy a file from:", localUrl, "to:", localUploadDirUrl)
                                }
                            }
                            self.share(url: nil, uploadedFileURL: remoteUrl);
                        });
                    } else {
                        self.showAlert(title: "Failure", message: "Please try again later.");
                    }
                })
            } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
                provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (value, error) in
                    self.share(url: (value as! URL), uploadedFileURL: nil);
                })
//            } else if provider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) {
//                provider.loadItem(forTypeIdentifier: kUTTypePlainText as String, options: nil, completionHandler: { (item, error) in
//                    self.share(text: item as! String);
//                });
//            } else {
//                self.showAlert(title: "Failure", message: "Please try again later.");
            }
        }
    }
    
    override func didSelectCancel() {
        xmppClient.disconnect(true);
        super.didSelectCancel();
    }
    
    override func configurationItems() -> [Any]! {
        return [accountConfigurationItem, buddiesConfigurationItem];
    }
    
    func showAlert(title: String, message: String) {
        DispatchQueue.main.async {
            let alert = UIAlertController(title: title, message: message, preferredStyle: .alert);
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {(action) in
                self.extensionContext?.cancelRequest(withError: ShareError.failure);
            }));
            self.present(alert, animated: true, completion: nil);
        }
    }
    
    func showAccountSelection() {
        if xmppClient.state != .disconnected {
            xmppClient.disconnect(true);
        }
        let controller = storyboard?.instantiateViewController(withIdentifier: "accountSelectionViewController") as! AccountsTableViewController;
//        let controller = AccountsTableViewController(style: .plain);
        controller.selected = account;
        controller.delegate = self;
        pushConfigurationViewController(controller);
    }
    
    func accountSelection(account: String) {
        self.account = account;
        self.recipients = [];
        validateContent();
        self.buddiesConfigurationItem.value = "";
        accountConfigurationItem.value = account;
        xmppClient.connectionConfiguration.setUserJID(BareJID(account)!);
        
        if let password = getAccountPassword() {
            xmppClient.connectionConfiguration.setUserPassword(password);
            if let rosterStore: RosterStore = xmppClient.sessionObject.getProperty(RosterModule.ROSTER_STORE_KEY) {
                rosterStore.cleared();
            }
            xmppClient.login();
        }
    }
    
    func showRecipientsSelection() {
        guard account != nil else {
            return;
        }
        let controller = storyboard?.instantiateViewController(withIdentifier: "recipientsSelectionViewController") as! RecipientsSelectionViewController;
        controller.selected = recipients;
        controller.xmppClient = xmppClient;
        controller.delegate = self;
        self.rosterController = controller;
        pushConfigurationViewController(controller);
    }
    
    func recipientsChanged(_ recipients: [JID]) {
        self.recipients = recipients;
        buddiesConfigurationItem.value = String(recipients.count);
        validateContent();
    }
    
    func getAccountPassword() -> String? {
        guard account != nil else {
            return nil;
        }
        let query: [String: NSObject] = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitOne, String(kSecReturnData) : kCFBooleanTrue, String(kSecAttrService) : "xmpp" as NSObject, String(kSecAttrAccount) : account! as NSObject ];
        
        var result:AnyObject?;
        
        let lastResultCode: OSStatus = withUnsafeMutablePointer(to: &result) {
            SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0));
        }
        
        if lastResultCode == noErr {
            if let data = result as? NSData {
                return String(data: data as Data, encoding: String.Encoding.utf8);
            }
        }
        return nil;
    }
    
    func upload(localUrl: URL, type: String?, handler: @escaping (URL?)->Void) {
        let size = try! FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as! UInt64;
        print("trying to upload", localUrl, "size", size, "type", type as Any);
        if let httpModule: HttpFileUploadModule = self.xmppClient.modulesManager.getModule(HttpFileUploadModule.ID) {
            httpModule.findHttpUploadComponent(onSuccess: { (results) in
                guard !results.isEmpty else {
                    self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
                    return;
                }
                
                let compJid = results.filter({ (k,v) -> Bool in
                    return v == nil || v! >= Int(size);
                }).first?.key;
                
                guard compJid != nil else {
                    self.showAlert(title: "Upload failed", message: "Selected object is too big!");
                    return;
                }
                
                httpModule.requestUploadSlot(componentJid: compJid!, filename: localUrl.pathComponents.last!, size: Int(size), contentType: type ?? "application/octet-stream", onSuccess: {(slot) in
                    print("allocated slot", slot.getUri, slot.putUri);
                    var request = URLRequest(url: slot.putUri);
                    slot.putHeaders.forEach({ (k,v) in
                        request.addValue(v, forHTTPHeaderField: k);
                    });
                    request.httpMethod = "PUT";
//                    let inputStream = InputStream(url: localUrl);
//                    request.httpBodyStream = inputStream;
                    request.addValue(type ?? "application/octet-stream", forHTTPHeaderField: "Content-Type");
                    
                    URLSession.shared.uploadTask(with: request, fromFile: localUrl) { (data, response, error) in
                        guard error == nil && ((response as? HTTPURLResponse)?.statusCode ?? 500) == 201 else {
                            print(data as Any, error as Any, response as Any);
                            self.showAlert(title: "Upload failed", message: "Upload to HTTP server failed.");
                            return;
                        }
                        handler(slot.getUri);
                    }.resume();
                }, onError: {(errorCondition, message) in
                    self.showAlert(title: "Upload failed", message: message ?? "Please try again later.");
                });
            }, onError: { (error) in
                if error != nil && error! == ErrorCondition.item_not_found {
                    self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
                } else {
                    self.showAlert(title: "Upload failed", message: "Please try again later.");
                }
            })
        } else {
            showAlert(title: "Upload failure", message: "Upload module not available!");
        }
    }
    
    func share(text: String? = nil, url: URL? = nil, uploadedFileURL: URL? = nil) {
        recipients.forEach { (recipient) in
            if !contentText.isEmpty || url != nil {
                let message = Message();
                message.type = StanzaType.chat;
                message.to = recipient;
                            
                if let text = text {
                    message.body = contentText.isEmpty ? text : "\(contentText!) - \(text)";
                } else if let url = url {
                    message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
                } else {
                    message.body = contentText;
                }
                xmppClient.context.writer?.write(message);
            }
            
            if let url = uploadedFileURL {
                let message = Message();
                message.type = .chat;
                message.to = recipient;
                message.oob = url.description;
                xmppClient.context.writer?.write(message);
            }
        }
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
            self.xmppClient.disconnect();
            self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
        });

    }
    
//    func shareText(url: URL?) {
//        print("sharing", contentText as Any, url);
//
//        recipients.forEach { (recipient) in
//            let message = Message();
//            message.type = StanzaType.chat;
//            message.to = recipient;
//            if let url = url {
//                message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
//                message.oob = url.description;
//            }
//            xmppClient.context.writer?.write(message);
//        }
//
//        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
//            self.xmppClient.disconnect();
//            self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
//        });
//    }
    
    class ShareEventHandler: EventHandler {
        
        weak var controller: ShareViewController?;
        
        func handle(event: Event) {
            switch event {
            case let e as RosterModule.ItemUpdatedEvent:
                DispatchQueue.main.async {
                    self.controller?.rosterController?.updateItem(item: e.rosterItem!);
                }
            default:
                break;
            }
        }
        
    }
    
    enum ShareError: Error {
        case firstRun
        case featureNotAvailable
        case tooBig
        case failure
        
    }
    
}
