//
//  APIClient.swift
//

import Foundation
import Alamofire
import ReactiveCocoa
import ReactiveSwift


/**
 통신 구현 객체
 
 - Parameters:
 - url: String not Optinal 기본주소
 - reqParam: dictionary Optinal 전달 파라메터
 - headerParam: dictionary Optinal 헤더 파라메터
 - fileParam: dictionary Optinal 파일 파라메터
 - progressShow: Bool 프로그래스바의 표현여부
 - entype: ENCTYPE 인코딩타입, default 기본 json option
 - ENCTYPE: alamofire의 인코딩 타입을 감추는 래퍼
 - callBack: async 호출일 경우 외부 클로저와 브릿징해준다
 
 */
class APIClient: NSObject {
    private var url:String!
    private var reqParam:[String:Any]?
    private var headerParam:[String:String]?
    private var fileParam:[[String:Data]]?
    private var progressShow:Bool = true
    private var entype:ENCTYPE = .default
    private var returnType:ENCTYPE = .json
    enum ENCTYPE{
        case `default`
        case json
        
        func getParameterEncoding() -> ParameterEncoding {
            switch self {
            case .default:
                return URLEncoding.default
            case .json:
                return JSONEncoding.default
            }
        }
    }
    private var method:METHOD = .post
    enum METHOD {
        case get
        case post
        
        func getHTTPMethod() -> HTTPMethod{
            switch self {
            case .get:
                return HTTPMethod.get
            case .post:
                return HTTPMethod.post
            }
        }
    }
    
    private var callBack = MutableProperty<Any?>(nil)
    
    /**
     초기화
     
     - Parameters:
     - url: String not Optinal 기본주소
     - reqParam: dictionary Optinal 전달 파라메터
     - headerParam: dictionary Optinal 헤더 파라메터
     - fileParam: dictionary Optinal 파일 파라메터
     
     */
    init(_ url:String) {
        super.init()
        self.url = url
    }
    
    /**
     파라메터설정
     
     - Parameters:
     - type: 딕셔너리
     
     - Returns: self
     */
    func param(reqParam:[String:Any]) -> APIClient {
        self.reqParam = reqParam
        return self
    }
    
    /**
     파라메터설정
     
     - Parameters:
     - type: 딕셔너리
     
     - Returns: self
     */
    func param(fileParam:[[String:Data]]) -> APIClient {
        self.fileParam = fileParam
        return self
    }
    
    /**
     파라메터설정
     
     - Parameters:
     - type: 딕셔너리
     
     - Returns: self
     */
    func param(headerParam:[String:String]) -> APIClient {
        self.headerParam = headerParam
        return self
    }
    
    /**
     인코딩 타입설정
     
     - Parameters:
     - type: ENCTYPE
     
     - Returns: self
     */
    func enType(_ type:ENCTYPE) -> APIClient {
        self.entype = type
        return self
    }
    
    /**
     http 타입설정
     
     - Parameters:
     - type: METHOD
     
     - Returns: self
     */
    func meThod(_ method:METHOD) -> APIClient {
        self.method = method
        return self
    }
    
    /**
     프로그래스바의 표현여부
     
     - Parameters:
     - isPro: Bool 표현여부
     
     - Returns: self
     */
    func isProgress(_ isPro:Bool) -> APIClient {
        self.progressShow = isPro
        return self
    }
    
    /**
     파일 업로드를 위한 펑션
     
     - Parameters:
     - multipartFormData: closure로 들어오는 upload 객체
     
     */
    func upload(multipartFormData:MultipartFormData){
        guard let files = self.fileParam else{ return }
        for file in files{
            for (name, data) in file{
                multipartFormData.append(data, withName: name, fileName: "temp.jpg", mimeType: self.mimeTypeForData(data:data))
            }
        }
        
        guard let params = self.reqParam else{ return }
        for (key, val) in params{
            if let strigVal = val as? String{
                multipartFormData.append(strigVal.data(using: .utf8)!, withName: key)
            }else{
                multipartFormData.append(String(describing: val).data(using: .utf8)!, withName: key)
            }
        }
    }
    
    /**
     콜백 리턴을 넣어주는 펑션
     
     - Parameters:
     - json: closure로 들어오는 DataResponse 객체
     
     */
    func responseJson(json:DataResponse<Any>){
        
        self.callBack.value = json.value as? [String:Any]
    }
    
    /**
     콜백 리턴을 넣어주는 펑션
     
     - Parameters:
     - data: closure로 들어오는 DataResponse 객체
     
     */
    func responseData(data:DataResponse<Data>){
        self.callBack.value = data.value
    }
    
    /**
     파일업로드 호출 펑션
     직접 alamofire를 호출
     */
    func multiUpload(){
        Alamofire.upload(multipartFormData: self.upload,
                         usingThreshold: UInt64(),
                         to: self.url,
                         method: self.method.getHTTPMethod(),
                         headers: self.headerParam,
                         encodingCompletion:{ encodingResult in
                            switch encodingResult {
                            case .success(let upload, _, _):
                                switch self.returnType {
                                case .default:
                                    upload.responseData(completionHandler: self.responseData)
                                case .json:
                                    upload.responseJSON(completionHandler: self.responseJson)
                                }
                            case .failure(let encodingError):
                                print("error:\(encodingError)")
                            }
        })
    }
    
    /**
     메인 비동기 호출 펑션
     fileParam이 있을경우 self.multiUpload()로 점프한다
     그외 인코딩 타입에 따라 리스폰스 받은 객체를 completionHandle로 전송한다.
     
     - Parameters:
     - completionHandle: 클로저를 받아 비동기 호출후 값을 담아 전송
     
     
     */
    typealias CallBackHandler<T> = (T) -> Void
    public func connect<T>(completionHandle:@escaping CallBackHandler<T>){
        
        if T.self == [String:Any].self {
            self.returnType = .json
        }else{
            self.returnType = .default
        }
        
        self.callBack.signal.observe { (item) in
            guard let value = item.value as? T else{ return }
            
            completionHandle(value)
        }
        
        guard self.fileParam == nil else {
            self.multiUpload()
            return
        }
        
        let api = Alamofire.request(self.url, method: self.method.getHTTPMethod(), parameters: self.reqParam, encoding: self.entype.getParameterEncoding(), headers: self.headerParam)
        switch self.returnType {
        case .default:
            api.responseData(completionHandler: self.responseData)
        case .json:
            api.responseJSON(completionHandler: self.responseJson)
        }
        
        
    }
    
    /**
     동기화 호출 펑션
     
     - Returns: DataResponse<Any> JSON OBJECT를 동기화 코드로 전송한다.
     */
    public func get<T>(type:T) -> DataResponse<T> {
        
        if T.self == [String:Any].self {
            self.returnType = .json
        }else{
            self.returnType = .default
        }
        
        let req = Alamofire.request(self.url, method: self.method.getHTTPMethod(), parameters: self.reqParam, encoding: self.entype.getParameterEncoding(), headers: self.headerParam)
        
        let semaphore = DispatchSemaphore(value: 0)
        var result: DataResponse<T>!
        var serializer: DataResponseSerializer<T>!
        switch self.returnType {
        case .default:
            serializer = DataRequest.dataResponseSerializer() as? DataResponseSerializer<T>
        case .json:
            serializer = DataRequest.jsonResponseSerializer() as? DataResponseSerializer<T>
        }
        
        req.response(queue: DispatchQueue.global(qos: .default), responseSerializer: serializer) { response in
            result = response as DataResponse<T>
            semaphore.signal()
        }
        
        _ = semaphore.wait(timeout: DispatchTime.distantFuture)
        
        return result
        
    }
    
    /**
     파일 타입을 알아내는 펑션
     
     - Parameters:
     - data: Data file 객체
     
     - Returns: String mime 타일을 반납한다. ex) image/jpeg
     */
    public func mimeTypeForData(data:Data) -> String{
        var c = [UInt32](repeating: 0, count: 1)
        (data as NSData).getBytes(&c, length: 1)
        switch (c[0]) {
        case 0xFF:
            return "image/jpeg"
        case 0x89:
            return "image/png"
        case 0x47:
            return "image/gif"
        case 0x49:
            return "image/tiff"
        case 0x4D:
            return "image/tiff"
        case 0x25:
            return "application/pdf"
        case 0xD0:
            return "application/vnd"
        case 0x46:
            return "text/plain"
        default:
            return "application/octet-stream"
        }
    }
    
    
}
