import Foundation

enum HTTPVersion: String {
    case version10 = "HTTP/1.0"
    case version11 = "HTTP/1.1"
}

extension HTTPVersion: CustomStringConvertible {
    // MARK: CustomStringConvertible
    var description: String {
        return rawValue
    }
}

// MARK: -
enum HTTPMethod: String {
    case get     = "GET"
    case post    = "POST"
    case put     = "PUT"
    case delete  = "DELETE"
    case head    = "HEAD"
    case options = "OPTIONS"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

// MARK: -
enum HTTPStatusCode: Int {
    case `continue`                   = 100
    case switchingProtocols           = 101
    case ok                           = 200
    case created                      = 201
    case accepted                     = 202
    case nonAuthoritative             = 203
    case noContent                    = 204
    case resetContent                 = 205
    case partialContent               = 206
    case multipleChoices              = 300
    case movedParmanently             = 301
    case found                        = 302
    case seeOther                     = 303
    case notModified                  = 304
    case useProxy                     = 305
    case temporaryRedirect	          = 307
    case badRequest                   = 400
    case unauthorixed                 = 401
    case paymentRequired              = 402
    case forbidden                    = 403
    case notFound                     = 404
    case methodNotAllowed             = 405
    case notAcceptable                = 406
    case proxyAuthenticationRequired  = 407
    case requestTimeOut               = 408
    case conflict                     = 409
    case gone                         = 410
    case lengthRequired               = 411
    case preconditionFailed	          = 412
    case requestEntityTooLarge        = 413
    case requestURITooLarge           = 414
    case unsupportedMediaType         = 415
    case requestedRangeNotSatisfiable = 416
    case expectationFailed            = 417
    case internalServerError          = 500
    case notImplemented               = 501
    case badGateway                   = 502
    case serviceUnavailable           = 503
    case gatewayTimeOut               = 504
    case httpVersionNotSupported      = 505

    var message: String {
        switch self {
        case .continue:
            return "Continue"
        case .switchingProtocols:
            return "Switching Protocols"
        case .ok:
            return "OK"
        case .created:
            return "Created"
        case .accepted:
            return "Accepted"
        case .nonAuthoritative:
            return "Non-Authoritative Information"
        case .noContent:
            return "No Content"
        case .resetContent:
            return "Reset Content"
        case .partialContent:
            return "Partial Content"
        case .multipleChoices:
            return "Multiple Choices"
        case .movedParmanently:
            return "Moved Permanently"
        case .found:
            return "Found"
        case .seeOther:
            return "See Other"
        case .notModified:
            return "Not Modified"
        case .useProxy:
            return "Use Proxy"
        case .temporaryRedirect:
            return "Temporary Redirect"
        case .badRequest:
            return "Bad Request"
        case .unauthorixed:
            return "Unauthorixed"
        case .paymentRequired:
            return "Payment Required"
        case .forbidden:
            return "Forbidden"
        case .notFound:
            return "Not Found"
        case .methodNotAllowed:
            return "Method Not Allowed"
        case .notAcceptable:
            return "Not"
        case .proxyAuthenticationRequired:
            return "Proxy Authentication Required"
        case .requestTimeOut:
            return "Request Time-out"
        case .conflict:
            return "Conflict"
        case .gone:
            return "Gone"
        case .lengthRequired:
            return "Length Required"
        case .preconditionFailed:
            return "Precondition Failed"
        case .requestEntityTooLarge:
            return "Request Entity Too Large"
        case .requestURITooLarge:
            return "Request-URI Too Large"
        case .unsupportedMediaType:
            return "Unsupported Media Type"
        case .requestedRangeNotSatisfiable:
            return "Requested range not satisfiable"
        case .expectationFailed:
            return "Expectation Failed"
        case .internalServerError:
            return "Internal Server Error"
        case .notImplemented:
            return "Not Implemented"
        case .badGateway:
            return "Bad Gateway"
        case .serviceUnavailable:
            return "Service Unavailable"
        case .gatewayTimeOut:
            return "Gateway Time-out"
        case .httpVersionNotSupported:
            return "HTTP Version not supported"
        }
    }
}

extension HTTPStatusCode: CustomStringConvertible {
    // MARK: CustomStringConvertible
    var description: String {
        return "\(rawValue) \(message)"
    }
}

// MARK: -
open class HTTPService: NetService {
    open class var type: String {
        return "_http._tcp"
    }
    open class var defaultPort: Int32 {
        return 8080
    }
    open class var defaultDocument: String {
        return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /><title>lf</title></head><body>lf</body></html>"
    }

    var document: String = HTTPService.defaultDocument

    func client(inputBuffer client: NetClient) {
        guard let request: HTTPRequest = HTTPRequest(data: client.inputBuffer) else {
            disconnect(client)
            return
        }
        client.inputBuffer.removeAll()
//        if logger.isEnabledFor(level: .trace) {
//            logger.trace("\(request): \(self)")
//        }
        switch request.method {
        case "GET":
            get(request, client: client)
        case "POST":
            post(request, client: client)
        case "PUT":
            put(request, client: client)
        case "DELETE":
            delete(request, client: client)
        case "HEAD":
            head(request, client: client)
        case "OPTIONS":
            options(request, client: client)
        case "TRACE":
            trace(request, client: client)
        case "CONNECT":
            connect(request, client: client)
        default:
            notFound(request, client: client)
        }
    }

    open func get(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func post(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func put(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func delete(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func head(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func options(_ requst: HTTPRequest, client: NetClient) {
        notFound(requst, client: client)
    }

    open func trace(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    open func connect(_ request: HTTPRequest, client: NetClient) {
        notFound(request, client: client)
    }

    func notFound(_ request: HTTPRequest, client: NetClient) {
        var response: HTTPResponse = HTTPResponse()
        response.statusCode = HTTPStatusCode.notFound.description
        client.doOutput(data: response.data)
    }
}

open class HLSService: HTTPService {
    private(set) var streams: [HTTPStream] = []

    open func addHTTPStream(_ stream: HTTPStream) {
        for i in 0..<streams.count where stream.name == streams[i].name {
            return
        }
        streams.append(stream)
    }

    open func removeHTTPStream(_ stream: HTTPStream) {
        for i in 0..<streams.count where stream.name == streams[i].name {
            streams.remove(at: i)
            return
        }
    }

    open override func get(_ request: HTTPRequest, client: NetClient) {
        //logger.trace("\(request)")
        var response: HTTPResponse = HTTPResponse()
        // #141
        response.headerFields["Access-Control-Allow-Headers"] = "*"
        response.headerFields["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS"
        response.headerFields["Access-Control-Allow-Origin"] = "*"
        response.headerFields["Access-Control-Expose-Headers"] = "*"
        response.headerFields["Connection"] = "close"

        defer {
            //logger.trace("\(response)")
            disconnect(client)
        }

        switch request.uri {
        case "/":
            response.headerFields["Content-Type"] = "text/html"
            response.body = Data(document.utf8)
            client.doOutput(data: response.data)
        default:
            for stream in streams {
                guard let (mime, resource) = stream.getResource(request.uri) else {
                    break
                }
                response.statusCode = HTTPStatusCode.ok.description
                response.headerFields["Content-Type"] = mime.rawValue
                switch mime {
                case .VideoMP2T:
                    if let info: [FileAttributeKey: Any] = try? FileManager.default.attributesOfItem(atPath: resource) {
                        if let length: Any = info[FileAttributeKey.size] {
                            response.headerFields["Content-Length"] = String(describing: length)
                        }
                    }
                    client.doOutput(data: response.data)
                    client.doOutputFromURL(URL(fileURLWithPath: resource), length: 8 * 1024)
                default:
                    response.statusCode = HTTPStatusCode.ok.description
                    response.body = Data(resource.utf8)
                    client.doOutput(data: response.data)
                }
                return
            }
            response.statusCode = HTTPStatusCode.notFound.description
            client.doOutput(data: response.data)
        }
    }
}
