//
//  Socks5ViewController.swift
//  MpAccDemo
//

import UIKit
import Alamofire
import MpAccSDK
import CocoaAsyncSocket
import MpLogPlugin
import DGCharts

fileprivate struct DemoRttData: CustomStringConvertible {
    var wifi: Int = 0
    var mobile: Int = 0
    var acc: Int = 0
    mutating func updateRtt(_ rtt: Int, type: Int) {
        if type == 0 {
            mobile = rtt
        } else if type == 1 {
            wifi = rtt
        } else if type == 2 {
            acc = rtt
        }
    }
    var description: String {
        "mobile:\(mobile) \t wifi:\(wifi) \t acc:\(acc)"
    }
}

class Socks5ViewController: UIViewController {

    @IBOutlet weak var tcpSendBtn: UIButton!
    @IBOutlet weak var udpSendBtn: UIButton!
    
    @IBOutlet weak var tcpSendSlider: UISlider!
    @IBOutlet weak var udpSendSlider: UISlider!
    
    @IBOutlet weak var tcpSendDurationLabel: UILabel!
    @IBOutlet weak var udpSendDurationLabel: UILabel!
    
    
    @IBOutlet weak var rttLabel: UILabel!
    @IBOutlet weak var totalLabel: UILabel!
    @IBOutlet weak var mobileLabel: UILabel!
    @IBOutlet weak var wifiLabel: UILabel!
    
    @IBOutlet weak var ipTextField: UITextField!
    @IBOutlet weak var portTextField: UITextField!
    @IBOutlet weak var messageField: UITextField!
    
    @IBOutlet weak var logView: LogTableView!
    private let logQueue = DispatchQueue(label: "com.tencent.mpacc.demo.log")
    
    private var rttData: DemoRttData = DemoRttData()
    
    private var afs: Session = AF
    
    var tcpSocket: GCDAsyncSocket?
    
    var udpSocket: GCDAsyncUdpSocket!
    var udpToken: UDPSocksToken?
    
    let socksHelper = SocketHelper()
    
    let workqueue = DispatchQueue(label: "com.tencent.mpacc.demo.socket")
    let operationQueue = OperationQueue()
    
    var isSendingTcp = false
    var isSendingUdp = false
    
    lazy var logPlugin: LogPlugin = {
        let setting = ConfigSetting.shared
        let plugin = LogPlugin(setting: .socks5(
            dataKey: setting.registerSetting.dataKey,
            deviceId: setting.registerSetting.deviceId,
            consoleEnabled: true,
            justAutoUploadInWiFi: true)
        )
        return plugin
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Socks5 acc"
        MpAccClient.shared.registerAccCallback(self)
        MpAccClient.shared.registerMeasureCallback(self)
        self.wifiLabel.font = .monospacedDigitSystemFont(ofSize: 16, weight: .medium)
        self.mobileLabel.font = .monospacedDigitSystemFont(ofSize: 16, weight: .medium)
        self.totalLabel.font = .monospacedDigitSystemFont(ofSize: 16, weight: .medium)
        self.totalLabel.adjustsFontSizeToFitWidth = true
        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapEmptyAction)))
        
        self.ipTextField.text = "112.46.13.235"
        self.portTextField.text = "22222"
        
        setupUdp()
        
        self.tcpSendBtn.setTitle("sendingTcp", for: .selected)
        self.tcpSendBtn.setTitle("startTcp", for: .normal)
        
        self.udpSendBtn.setTitle("sendingUdp", for: .selected)
        self.udpSendBtn.setTitle("startUdp", for: .normal)
        
        self.tcpSendDurationLabel.text = "\(UInt32(self.tcpSendSlider.value))ms"
        self.udpSendDurationLabel.text = "\(UInt32(self.udpSendSlider.value))ms"
        
        self.rttLabel.text = self.rttData.description
        
        self.rttLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(gotoRttPage(sender:))))
        self.rttLabel.isUserInteractionEnabled = true
        self.rttLabel.backgroundColor = .lightGray
        
        EmptyAudioManager.share.playEmptyMusic()
    }
    
    @objc func gotoRttPage(sender: UITapGestureRecognizer) {
        let rttVC = RttChartsViewController()
        present(rttVC, animated: true)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let setting = ConfigSetting.shared
        
        MpAccClient.shared.setupLogLevel(setting.logLevel)
        if setting.publicMode {
            let registerParam = ConfigSetting.getRegisterParam()
            MpAccClient.shared.setupDatakey(registerParam.dataKey, deviceId: registerParam.deviceId)
        } else {
            MpAccClient.shared.setupDatakey("", deviceId: "")
        }
        if setting.useLogPlugin {
            AccPluginManager.shared.setLogPlugin((self.logPlugin))
        } else {
            AccPluginManager.shared.setLogPlugin(nil)
        }
    }
    
    deinit {
        self.udpSocket.close()
        EmptyAudioManager.share.stop()
    }
    
    @objc func tapEmptyAction() {
        self.view.endEditing(true)
    }
}

extension Socks5ViewController {
    func appendLog(funcName: String = #function, _ log: String) {
        logQueue.async {
            let logStr = funcName + log
            print(logStr)
            if ConfigSetting.shared.displayRollingLog {
                self.logView.appendLog(logStr: logStr)
            }
        }
    }
}

extension Socks5ViewController {
    
    
    @IBAction func preRegister(_ sender: UIButton) {
        let mode = AccMode(rawValue: ConfigSetting.shared.accMode)!
        MpAccClient.shared.register(mode: mode) { error in
            self.appendLog("\(String(describing: error))")
        }
    }
    
    @IBAction func startMeasure(_ sender: UIButton) {
        let measureConfig = ConfigSetting.getMeasureConfig()
        MpAccClient.shared.startMeasure(measureConfig: measureConfig)
    }
    
    @IBAction func stopMeasure(_ sender: UIButton) {
        MpAccClient.shared.stopMeasure()
    }
    
    @IBAction func startAcc(_ sender: UIButton) {
        let accConfig = ConfigSetting.getAccConfig()
        MpAccClient.shared.start(config: accConfig)
    }
    
    @IBAction func stopAcc(_ sender: UIButton) {
        afs = AF
        tcpSocket = nil
        udpToken = nil
        MpAccClient.shared.stop()
    }
    
    @IBAction func httpRequestAction(_ sender: UIButton) {
        afs.request("https://www.taobao.com").response { dataResponse in
            switch dataResponse.result {
            case .success(let rst):
                self.appendLog("taobao:\(String(describing: rst))")
            case .failure(let err):
                self.appendLog("taobao error:\(String(describing: err))")
            }
        }
        afs.request("https://www.baidu.com").response { dataResponse in
            switch dataResponse.result {
            case .success(let rst):
                self.appendLog("baidu:\(String(describing: rst))")
            case .failure(let err):
                self.appendLog("baidu error:\(String(describing: err))")
            }
        }
        afs.request("https://www.jd.com").response { dataResponse in
            switch dataResponse.result {
            case .success(let rst):
                self.appendLog("jd:\(String(describing: rst))")
            case .failure(let err):
                self.appendLog("jd error:\(String(describing: err))")
            }
        }
    }
    
    @IBAction func tcpRequestAction(_ sender: UIButton) {
        sender.isSelected = !sender.isSelected
        self.isSendingTcp = sender.isSelected
        self.tcpSendSlider.isEnabled = !sender.isSelected
        if !sender.isSelected {
            return
        }
        let willSendMsg = self.messageField.text!
        let host = self.ipTextField.text!.trimmingCharacters(in: .whitespaces)
        let port = UInt16(self.portTextField.text!.trimmingCharacters(in: .whitespaces))!
        
        let setting = ConfigSetting.shared
        let socksPort = setting.socksProxyPort
        let username = setting.socksUsername
        let password = setting.socksPassword
        
        let duration = UInt32(self.tcpSendSlider.value * 1000)
        
        DispatchQueue.global().async {
            var tag = 0;
            while self.isSendingTcp {
                do {
                    tag += 1
                    if MpAccClient.shared.isStart {
                        var error: NSError?
                        let socketFd = self.socksHelper.connectToTcpSocket(
                            ipv4: host,
                            port: port,
                            withSocksProxyPort: socksPort,
                            username: username,
                            password: password,
                            error: &error
                        )
                        if let error = error {
                            throw error
                        }
                        let tcpOperation = try TCPSendOperation(msg: willSendMsg, socketFd: socketFd, tag: tag, workqueue: self.workqueue) { logStr in
                            self.appendLog(funcName: "", logStr)
                        }
                        self.operationQueue.addOperation(tcpOperation)
                    } else {
                        let tcpOperation = try TCPSendOperation(msg: willSendMsg, host: host, port: port, tag: tag, workqueue: self.workqueue) { logStr in
                            self.appendLog(funcName: "", logStr)
                        }
                        self.operationQueue.addOperation(tcpOperation)
                    }
                } catch let err {
                    self.appendLog("\n create tcp socket err:\(err)")
                }
                usleep(duration)
            }
        }
    }
    
    @IBAction func udpRequestAction(_ sender: UIButton) {
        sender.isSelected = !sender.isSelected
        self.isSendingUdp = sender.isSelected
        self.udpSendSlider.isEnabled = !sender.isSelected
        if !sender.isSelected {
            return
        }
        let willSendMsg = self.messageField.text!
        let duration = UInt32(self.udpSendSlider.value * 1000)
        
        DispatchQueue.global().async {
            var tag = 0;
            while self.isSendingUdp {
                let tmpTag = tag
                self.operationQueue.addBarrierBlock {
                    DispatchQueue.main.async {
                        self.sendUdpMsg(msg: willSendMsg, tag: tmpTag)
                    }
                }
                tag += 1
                usleep(duration)
            }
        }
    }
    
    @IBAction func tcpDurationChanged(_ sender: UISlider) {
        self.tcpSendDurationLabel.text = "\(UInt32(self.tcpSendSlider.value))ms"
    }
    
    @IBAction func udpDurationChanged(_ sender: UISlider) {
        self.udpSendDurationLabel.text = "\(UInt32(self.udpSendSlider.value))ms"
    }
}

extension Socks5ViewController: AccCallback {
    func onAccSuccess(ip: String, port: Int) -> Void {
        appendLog("ip: \(ip) port:\(port)")
        
        let sessionConfig = URLSessionConfiguration.default
        let setting = ConfigSetting.shared
        
        var proxy: [AnyHashable : Any] = [
            kCFStreamPropertySOCKSProxyHost : "127.0.0.1",
            kCFStreamPropertySOCKSProxyPort : ConfigSetting.shared.socksProxyPort,
        ]
        
        if let username = setting.socksUsername, let password = setting.socksPassword {
            proxy[kCFStreamPropertySOCKSUser] = username
            proxy[kCFStreamPropertySOCKSPassword] = password
        }
        
        sessionConfig.connectionProxyDictionary = proxy
        
        afs = Session(configuration: sessionConfig)
        
        DispatchQueue.main.async {
            let setting = ConfigSetting.shared
            var error: NSError?
            self.udpToken = self.socksHelper.createUdpSocket(
                targetIPv4: self.ipTextField.text!.trimmingCharacters(in: .whitespaces),
                port: UInt16(self.portTextField.text!.trimmingCharacters(in: .whitespaces))!,
                withSocksProxyPort: ConfigSetting.shared.socksProxyPort,
                username: setting.socksUsername,
                password: setting.socksPassword,
                error: &error
            )
            if let error = error {
                self.appendLog("create udp socket err: \(error)")
            } else {
                self.appendLog("create udp socket success")
            }
        }
    }
    
    func onAccFail(_ error: NSError) -> Void {
        appendLog("\(error)")
    }
    
    func onAccDataUpdate(tRx: Int64, tTx: Int64, pathDetails: [MpPathDetail]) -> Void {
        DispatchQueue.main.async {
            var tx:Int64 = 0, rx:Int64 = 0
            var infos = ""
            pathDetails.forEach { item in
                let info = String(format: "tx:%0.4d rx:%0.4d loss:%.2f rtt:%d", item.txBytes, item.rxBytes, item.lost, item.RTT)
                tx += item.txBytes
                rx += item.rxBytes
                switch item.type {
                case .mobile:
                    self.mobileLabel.text = info
                    infos += "\n\t mobile:" + info
                case .wifi:
                    self.wifiLabel.text = info
                    infos += "\n\t wifi:" + info
                case .acc:break
                @unknown default: break
                }
            }
            let total = "上行: \(tx / 1024) KB" +
            "下行: \(rx / 1024) KB" +
            "总发: \(tTx / 1024) KB" +
            "总收: \(tRx / 1024) KB"
            
            self.totalLabel.text = total
        }
    }
    
    func onSummaryInfoUpdate(_ summaryInfo: String) -> Void {
        appendLog(summaryInfo)
    }
    
    func onNetworkStateChanged(_ type: MpInterfaceType, available: Bool, ip: String) {
        appendLog("type: \(type) available: \(available) ip: \(ip)")
    }
}

extension Socks5ViewController: MpMeasureCallback {

    func onStartMpAcc(code: Int) {
        appendLog("code:\(code)")
    }

    func onStopMpAcc(code: Int, msg: String) {
        appendLog("code:\(code)")
    }

    func onNoPolicy(code: Int, message: String?) {
        appendLog("code:\(code) msg: \(message ?? "")")
    }

    func onAccException(errorCode: Int, msg: String) {
        appendLog("errorCode:\(errorCode) msg: \(msg)")
    }

    func onRttChanged(type: Int, rtt: Int) {
        appendLog("type:\(type) rtt:\(rtt)")
        self.rttData.updateRtt(rtt, type: type)
        DispatchQueue.main.async {
            self.rttLabel.text = self.rttData.description
        }
    }

    func onAccStateChanged(state: Bool, code: Int) {
        appendLog("state:\(state) code:\(code)")
    }
}

// MARK: GCDAsyncUdpSocketDelegate
extension Socks5ViewController: GCDAsyncUdpSocketDelegate {
    
    func setupUdp() {
        DispatchQueue.global().async {
            do {
                let port = try self.socksHelper.getSocksPort()
                self.udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: self.workqueue)
                
                try self.udpSocket.bind(toPort: port)
                try self.udpSocket.enableBroadcast(true)
                try self.udpSocket.beginReceiving()
            } catch {
                DispatchQueue.main.async {
                    let alert = UIAlertController(title: nil, message: "UDP绑定播放端口失败(\(error))，请重新进入页面", preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { action in
                        if let nav = self.navigationController {
                            nav.popViewController(animated: true)
                        } else {
                            self.dismiss(animated: true)
                        }
                    }))
                    self.present(alert, animated: true)
                }
            }
        }
    }
    
    func sendUdpMsg(msg: String, tag: Int) {
        let orgData = msg.data(using: .utf8)!
        if let token = udpToken {
            let data = token.createUdpFrame(orgData: orgData)
            self.udpSocket.send(data, toHost: token.proxyIp, port: token.proxyPort, withTimeout: -1, tag: tag)
        } else {
            self.udpSocket.send(orgData, toHost: self.ipTextField.text!, port: UInt16(self.portTextField.text!)!, withTimeout: -1, tag: tag)
        }
    }
    
    func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
        let ip = GCDAsyncUdpSocket.host(fromAddress: address)!
        let port = GCDAsyncUdpSocket.port(fromAddress: address)
        let str: String
        if let udpToken = udpToken {
            str = String(data: udpToken.getOrgDataFromDatagram(data,from: ip, port: port), encoding: .utf8)!
        } else {
            str = String(data: data, encoding: .utf8)!
        }
        appendLog(funcName: "", "[UDP] receive udp Msg: \(str) \n\t from ip: \(ip) port:\(port)")
    }
    
    func udpSocket(_ sock: GCDAsyncUdpSocket, didSendDataWithTag tag: Int) {
        appendLog(funcName: "", "[UDP][\(tag)] has sended Msg")
    }
    
}

