// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

// TODO: Update license. Reproduced from: https://github.com/apple/swift-nio-examples/tree/master/json-rpc/Sources/JsonRpc

import Foundation
import NIO

public final class ChannelServer {
    private let group: MultiThreadedEventLoopGroup
    private var channel: Channel?
    private let handler: RPCClosure

    public init(group: MultiThreadedEventLoopGroup, handler: @escaping RPCClosure) {
        self.group = group
        self.handler = handler
        self.state = .initializing
    }

    deinit {
        assert(self.state == .stopped)
    }

    public func start() -> EventLoopFuture<ChannelServer> {
        assert(state == .initializing)

        let bootstrap = NIOPipeBootstrap(group: group)
            .channelInitializer { channel in
                channel.pipeline.addFramingHandlers(framing: .jsonpos)
                    .flatMap {
                        channel.pipeline.addHandler(CodableCodec<JSONRequest, JSONResponse>(), name: "AutorestServer")
                    }.flatMap {
                        channel.pipeline.addHandler(
                            ServerHandler(self.handler),
                            name: "ServerHandler"
                        )
                    }
            }.withPipes(inputDescriptor: STDIN_FILENO, outputDescriptor: STDOUT_FILENO)

        state = .starting
        return bootstrap.eventLoop.makeSucceededFuture(self)
    }

    public func stop() -> EventLoopFuture<Void> {
        SharedLogger.info("Server stop")
        if state != .started {
            return group.next().makeFailedFuture(ServerError.notReady)
        }
        guard let channel = self.channel else {
            return group.next().makeFailedFuture(ServerError.notReady)
        }
        state = .stopping
        channel.closeFuture.whenComplete { _ in
            self.state = .stopped
        }
        return channel.close()
    }

    private var _state = ChannelState.initializing
    private let lock = NSLock()
    private var state: ChannelState {
        get {
            return lock.withLock {
                _state
            }
        }
        set {
            lock.withLock {
                _state = newValue
            }
        }
    }
}

private class ServerHandler: ChannelInboundHandler, RemovableChannelHandler {
    public typealias InboundIn = JSONRequest
    public typealias OutboundOut = JSONResponse

    private let closure: RPCClosure

    public init(_ closure: @escaping RPCClosure) {
        self.closure = closure
    }

    public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        SharedLogger.info("Server channelRead")
        let request = unwrapInboundIn(data)
        closure(context, request.id, request.method, RPCObject(request.params)) { result in
            let response: JSONResponse
            switch result {
            case let .success(handlerResult):
                SharedLogger.debug("RPC handler returned success \(handlerResult)")
                response = JSONResponse(id: request.id ?? 0, result: handlerResult)
            case let .failure(handlerError):
                SharedLogger.error("RPC handler returned failure \(handlerError)")
                response = JSONResponse(id: request.id ?? 0, error: handlerError)
            }
            context.channel.writeAndFlush(self.wrapOutboundOut(response), promise: nil)
        }
    }

    public func errorCaught(context: ChannelHandlerContext, error: Error) {
        SharedLogger.error("Server Handler errorCaught. error=\(error)")

        switch error {
        case CodecError.badFraming, CodecError.badJSON:
            let response = JSONResponse(id: 0, errorCode: .parseError, error: error)
            context.channel.writeAndFlush(wrapOutboundOut(response), promise: nil)
        case CodecError.requestTooLarge:
            let response = JSONResponse(id: 0, errorCode: .invalidRequest, error: error)
            context.channel.writeAndFlush(wrapOutboundOut(response), promise: nil)
        default:
            let response = JSONResponse(id: 0, errorCode: .internalError, error: error)
            context.channel.writeAndFlush(wrapOutboundOut(response), promise: nil)
        }
        // close the client connection
        context.close(promise: nil)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

// TODO: Update license. Reproduced from: https://github.com/apple/swift-nio-examples/tree/master/json-rpc/Sources/JsonRpc

import Foundation
import NIO

public enum ResultType<Value, Error> {
    case success(Value)
    case failure(Error)
}

public enum Framing: CaseIterable {
    case `default`
    case jsonpos
    case brute
}

internal extension NSLock {
    func withLock<T>(_ body: () -> T) -> T {
        lock()
        defer {
            self.unlock()
        }
        return body()
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema types that are any types
typealias AnySchema = Schema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class ArraySchema: Schema {
    /// elementType of the array
    let elementType: Schema!

    /// maximum number of elements in the array
    let maxItems: Int?

    /// minimum number of elements in the array
    let minItems: Int?

    /// if the elements in the array should be unique
    let uniqueItems: Bool?

    /// if elements in the array should be nullable
    let nullableItems: Bool?

    enum CodingKeys: String, CodingKey {
        case elementType, maxItems, minItems, uniqueItems, nullableItems
    }

    // MARK: Codable

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.elementType = try Schema.decode(withContainer: container, useKey: "elementType")
        self.maxItems = try? container.decode(Int.self, forKey: .maxItems)
        self.minItems = try? container.decode(Int.self, forKey: .minItems)
        self.uniqueItems = try? container.decode(Bool.self, forKey: .uniqueItems)
        self.nullableItems = try? container.decode(Bool.self, forKey: .nullableItems)

        try super.init(from: decoder)
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(elementType, forKey: .elementType)
        if maxItems != nil { try container.encode(maxItems, forKey: .maxItems) }
        if minItems != nil { try container.encode(minItems, forKey: .minItems) }
        if uniqueItems != nil { try container.encode(uniqueItems, forKey: .uniqueItems) }
        if nullableItems != nil { try container.encode(nullableItems, forKey: .nullableItems) }

        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a boolean value
typealias BinarySchema = Schema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema types that are non-object or complex types
typealias BooleanSchema = PrimitiveSchema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

enum ByteArrayFormat: String, Codable {
    case base64url
    case byte
}

/// a schema that represents a ByteArray value
class ByteArraySchema: ValueSchema {
    /// date-time format
    let format: ByteArrayFormat

    enum CodingKeys: String, CodingKey {
        case format
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        format = try container.decode(ByteArrayFormat.self, forKey: .format)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(format, forKey: .format)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a Char value
typealias CharSchema = PrimitiveSchema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a choice of several values (ie, an 'enum')
class ChoiceSchema: ValueSchema, EnumerableSchema {
    /// the primitive type for the choices
    let choiceType: PrimitiveSchema

    /// the possible choices for in the set
    let choices: [ChoiceValue]

    enum CodingKeys: String, CodingKey {
        case choiceType, choices
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        choiceType = try container.decode(PrimitiveSchema.self, forKey: .choiceType)
        choices = try container.decode([ChoiceValue].self, forKey: .choices)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(choiceType, forKey: .choiceType)
        try container.encode(choices, forKey: .choices)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// Model that contains all the information required to generate a service API
class CodeModel: Codable, LanguageShortcut {
    let info: Info
    let schemas: Schemas
    let operationGroups: [OperationGroup]
    let globalParameters: [ParameterType]?
    let security: Security
    var language: Languages
    let `protocol`: Protocols
    let extensions: [String: AnyCodable]?

    /// Lookup a schema by name.
    func schema(for name: String, withType type: AllSchemaTypes) -> Schema? {
        return schemas.schema(for: name, withType: type)
    }

    /// Lookup a global parameter by name.
    func globalParameter(for name: String) -> ParameterType? {
        return globalParameters?.first(named: name)
    }

    /// Lookup a model by name
    func object(for name: String) -> ObjectSchema? {
        let values = schemas.objects?.filter { $0.name == name } ?? []
        assert(values.count <= 1, "Expected, at most, one item.")
        return values.first
    }

    func getApiVersion() -> String {
        let apiNames = ["apiVersion", "x-ms-version"]

        for apiName in apiNames {
            if let constantSchema = globalParameter(for: apiName)?.schema as? ConstantSchema {
                return constantSchema.value.value
            }
        }
        return ""
    }

    var pagingNames: Language.PagingNames? {
        for group in operationGroups {
            for operation in group.operations {
                if let pagingMetadata = operation.extensions?["x-ms-pageable"]?.value as? [String: String] {
                    return Language.PagingNames(from: pagingMetadata)
                }
            }
        }
        return nil
    }
}

extension CodeModel {
    var packageName: String {
        let strippedSuffixes = ["service", "client"]
        var nameComps = name.splitAndJoinAcronyms

        while nameComps.count > 0 {
            guard let last = nameComps.last else { break }
            if strippedSuffixes.contains(last) {
                nameComps = Array(nameComps.dropLast())
                continue
            } else {
                break
            }
        }
        // capitalize each word and join
        for (index, comp) in nameComps.enumerated() {
            nameComps[index] = comp.capitalized
        }
        return nameComps.joined()
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// schema types that can be objects
typealias ComplexSchema = Schema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a value dependent on another
class ConditionalSchema: ValueSchema {
    /// the primitive type for the conditional
    let conditionalType: PrimitiveSchema

    /// the possible conditional values
    let conditions: [ConditionalValue]

    /// the source value that drives the target value (property or parameter)
    let sourceValue: Value

    enum CodingKeys: String, CodingKey {
        case conditionalType, conditions, sourceValue
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        conditionalType = try container.decode(PrimitiveSchema.self, forKey: .conditionalType)
        conditions = try container.decode([ConditionalValue].self, forKey: .conditions)
        sourceValue = try container.decode(Value.self, forKey: .sourceValue)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(conditionalType, forKey: .conditionalType)
        try container.encode(conditions, forKey: .conditions)
        try container.encode(sourceValue, forKey: .sourceValue)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a constant value
class ConstantSchema: Schema {
    /// a container for the actual constant value
    class ConstantValue: Codable, OptionalLanguageShortcut {
        /// per-language information for this value
        public var language: Languages?

        /// the actual constant value to use
        let value: String

        /// Additional metadata extensions dictionary
        let extensions: [String: AnyCodable]?

        enum CodingKeys: String, CodingKey {
            case language, value, extensions
        }

        public required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            language = try? container.decode(Languages?.self, forKey: .language)
            value = try container.decode(String.self, forKey: .value)
            extensions = try? container.decode([String: AnyCodable].self, forKey: .extensions)
        }

        public func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            if language != nil { try? container.encode(language, forKey: .language) }
            try container.encode(value, forKey: .value)
            if extensions != nil { try container.encode(extensions, forKey: .extensions) }
        }
    }

    /// the schema type of the constant value (ie, StringSchema, NumberSchema, etc)
    let valueType: Schema

    /// the constant value
    let value: ConstantValue

    enum CodingKeys: String, CodingKey {
        case valueType, value
    }

    enum ValueCodingKeys: String, CodingKey {
        case value
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        valueType = try Schema.decode(withContainer: container, useKey: "valueType")!
        self.value = try container.decode(ConstantValue.self, forKey: .value)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(valueType, forKey: .valueType)

        var valueContainer = container
            .nestedContainer(keyedBy: ValueCodingKeys.self, forKey: .value)

        switch valueType.type {
        case .boolean:
            try valueContainer.encode(Bool(value.value), forKey: .value)
        case .integer:
            try valueContainer.encode(Int(value.value), forKey: .value)
        case .number:
            if let numberSchema = valueType as? NumberSchema {
                // For number in format like '-1.034e-20', convert to a Double before encode so that it does not wrapped with "" in the output
                if numberSchema.precision > 32 || value.value.contains("e") {
                    try valueContainer.encode(Double(value.value), forKey: .value)
                } else {
                    try valueContainer.encode(value.value, forKey: .value)
                }
            } else {
                try valueContainer.encode(value.value, forKey: .value)
            }
        default:
            try valueContainer.encode(value.value, forKey: .value)
        }

        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a credential value
class CredentialSchema: PrimitiveSchema {
    /// the maximum length of the string
    let maxLength: Int?

    /// the minimum length of the string
    let minLength: Int?

    /// a regular expression that the string must be validated against
    let pattern: String?

    enum CodingKeys: String, CodingKey {
        case maxLength, minLength, pattern
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        maxLength = try? container.decode(Int?.self, forKey: .maxLength)
        minLength = try? container.decode(Int?.self, forKey: .minLength)
        pattern = try? container.decode(String?.self, forKey: .pattern)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if maxLength != nil { try? container.encode(maxLength, forKey: .maxLength) }
        if minLength != nil { try? container.encode(minLength, forKey: .minLength) }
        if pattern != nil { try? container.encode(pattern, forKey: .pattern) }
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a Date value
typealias DateSchema = PrimitiveSchema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

enum DateTimeFormat: String, Codable {
    case dateTime = "date-time"
    case dateTimeRfc1123 = "date-time-rfc1123"
}

/// a schema that represents a DateTime value
class DateTimeSchema: ValueSchema {
    /// date-time format
    let format: DateTimeFormat

    enum CodingKeys: String, CodingKey {
        case format
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        format = try container.decode(DateTimeFormat.self, forKey: .format)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(format, forKey: .format)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a key-value collection
class DictionarySchema: ComplexSchema {
    /// the element type of the dictionary. (Keys are always strings)
    let elementType: Schema

    /// if elements in the dictionary should be nullable
    let nullableItems: Bool?

    enum CodingKeys: String, CodingKey {
        case elementType, nullableItems
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        elementType = try Schema.decode(withContainer: container, useKey: "elementType")!
        self.nullableItems = try? container.decode(Bool?.self, forKey: .nullableItems)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(elementType, forKey: .elementType)
        if nullableItems != nil { try? container.encode(nullableItems, forKey: .nullableItems) }
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a Duration value
typealias DurationSchema = PrimitiveSchema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

// Simple protocol to denote common interface between
// ChoiceSchema and SealedChoiceSchema
protocol EnumerableSchema: LanguageShortcut {
    var choiceType: PrimitiveSchema { get }
    var choices: [ChoiceValue] { get }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class FlagSchema: ValueSchema {
    /// the possible choices for in the set
    let choices: [FlagValue]

    enum CodingKeys: String, CodingKey {
        case choices
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        choices = try container.decode([FlagValue].self, forKey: .choices)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(choices, forKey: .choices)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class GroupProperty: Property {
    let originalParameter: [ParameterType]

    enum CodingKeys: String, CodingKey {
        case originalParameter
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        originalParameter = try container.decode([ParameterType].self, forKey: .originalParameter)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(originalParameter, forKey: .originalParameter)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class GroupSchema: Schema, UsageSchema {
    /// contexts in which the schema is used
    let usage: [SchemaContext]?

    /// Known media types in which this schema can be serialized
    let serializationFormats: [KnownMediaType]?

    enum CodingKeys: String, CodingKey {
        case usage, serializationFormats
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        usage = try? container.decode([SchemaContext].self, forKey: .usage)
        serializationFormats = try? container.decode([KnownMediaType].self, forKey: .serializationFormats)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if usage != nil { try container.encode(usage, forKey: .usage) }
        if serializationFormats != nil { try container.encode(serializationFormats, forKey: .serializationFormats) }
        try super.encode(to: encoder)
    }

    func name(equals string: String) -> Bool {
        return name.caseInsensitiveCompare(string) == .orderedSame
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// code model metadata for HTTP protocol
class HttpModel: ProtocolInterface {
    /// a collection of security requirements for the service
    let security: [SecurityRequirement]?
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// extended metadata for HTTP operation parameters
class HttpParameter: ProtocolInterface {
    /// the location that this parameter is placed in the http request
    let `in`: ParameterLocation

    /// the Serialization Style used for the parameter
    let style: SerializationStyle?

    /// when set, 'form' style parameters generate separate parameters for each value of an array
    let explode: Bool?

    /// when set, this indicates that the content of the parameter should not be subject to URI encoding rules
    let skipUriEncoding: Bool?
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class HttpRequest: Codable, ProtocolInterface {
    // A relative path to an individual endpoint. \n\nThe field name MUST begin with a slash. \nThe path is
    // appended (no relative URL resolution) to the expanded URL from the Server Object's url field in order
    // to construct the full URL. \nPath templating is allowed. \n\nWhen matching URLs, concrete
    // (non-templated) paths would be matched before their templated counterparts.
    let path: String

    /// the base URI template for the operation. This will be a template that has Uri parameters to craft the base url to use.
    let uri: String

    // the HTTP Method used to process this operation
    let method: HttpMethod
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class HttpResponse: ProtocolInterface {
    // the possible HTTP status codes that this response MUST match one of
    let statusCodes: [StatusCode]

    // canonical response type (ie, 'json')
    let knownMediaType: KnownMediaType?

    // content returned by the service in the HTTP headers
    let mediaTypes: [String]?

    // content returned by the service in the HTTP headers
    let headers: [HttpHeader]?

    // sets of HTTP headers grouped together into a single schema
    let headerGroups: [GroupSchema]?
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

class HttpWithBodyRequest: HttpRequest {
    // canonical response type (ie, 'json')
    let knownMediaType: KnownMediaType

    // content returned by the service in the HTTP headers
    let mediaTypes: [String]

    // content returned by the service in the HTTP headers
    let headers: [HttpHeader]?

    // sets of HTTP headers grouped together into a single schema
    let headerGroups: [GroupSchema]?

    enum CodingKeys: String, CodingKey {
        case knownMediaType, mediaTypes, headers, headerGroups
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        knownMediaType = try container.decode(KnownMediaType.self, forKey: .knownMediaType)
        mediaTypes = try container.decode([String].self, forKey: .mediaTypes)
        headers = try? container.decode([HttpHeader].self, forKey: .headers)
        headerGroups = try? container.decode([GroupSchema].self, forKey: .headerGroups)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(knownMediaType, forKey: .knownMediaType)
        try container.encode(mediaTypes, forKey: .mediaTypes)

        if headers != nil { try container.encode(headers, forKey: .headers) }
        if headerGroups != nil { try container.encode(headerGroups, forKey: .headerGroups) }

        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// Common pattern for Metadata on aspects
class Metadata: Codable, LanguageShortcut {
    /// per-language information for this aspect
    public var language: Languages

    /// per-protocol information for this aspect
    let `protocol`: Protocols

    /// additional metadata extensions dictionary
    let extensions: [String: AnyCodable]?
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a Number value
class NumberSchema: PrimitiveSchema {
    /// precision (# of bits?) of the number
    let precision: Int

    /// if present, the number must be an exact multiple of this value
    let multipleOf: Int?

    /// if present, the value must be lower than or equal to this (unless exclusiveMaximum is true)
    let maximum: Int?

    /// if present, the value must be lower than maximum
    let exclusiveMaximum: Bool?

    /// if present, the value must be highter than or equal to this (unless exclusiveMinimum is true)
    let minimum: Int?

    /// if present, the value must be higher than minimum
    let exclusiveMinimum: Bool?

    // MARK: Codable

    enum CodingKeys: String, CodingKey {
        case precision, multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        precision = try container.decode(Int.self, forKey: .precision)
        multipleOf = try? container.decode(Int.self, forKey: .multipleOf)
        maximum = try? container.decode(Int.self, forKey: .maximum)
        exclusiveMaximum = try? container.decode(
            Bool.self,
            forKey:
            .exclusiveMaximum
        )

        minimum = try? container.decode(Int.self, forKey: .minimum)
        exclusiveMinimum = try? container.decode(Bool.self, forKey: .exclusiveMinimum)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(precision, forKey: .precision)
        if multipleOf != nil { try container.encode(multipleOf, forKey: .multipleOf) }
        if maximum != nil { try container.encode(maximum, forKey: .maximum) }
        if exclusiveMaximum != nil { try container.encode(exclusiveMaximum, forKey: .exclusiveMaximum) }
        if minimum != nil { try container.encode(minimum, forKey: .minimum) }
        if exclusiveMinimum != nil { try container.encode(exclusiveMinimum, forKey: .exclusiveMinimum) }

        try super.encode(to: encoder)
    }

    override func swiftType(parentName _: String? = nil) -> String {
        var swiftType: String
        switch precision {
        case 32:
            swiftType = (type == .integer) ? "Int32" : "Float"
        case 64:
            swiftType = (type == .integer) ? "Int64" : "Double"
        case 128:
            swiftType = (type == .integer) ? "Int" : "Decimal"
        default:
            swiftType = (type == .integer) ? "Int" : "Double"
        }
        return swiftType
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a type with child properties.
class ObjectSchema: ComplexSchema, UsageSchema {
    /// the property of the polymorphic descriminator for this type, if there is one
    let discriminator: Discriminator?

    /// maximum number of properties permitted
    let maxProperties: Int?

    /// minimum number of properties permitted
    let minProperties: Int?

    let parents: Relations?

    let children: Relations?

    let discriminatorValue: String?

    // MARK: allOf: Schema Usage

    let usage: [SchemaContext]?

    /// Known media types in which this schema can be serialized
    let serializationFormats: [KnownMediaType]?

    /// Returns the properties of the model and any parent models.
    var flattenedProperties: [PropertyType]? {
        var props = [PropertyType]()
        for prop in properties ?? [] {
            props.append(prop)
        }
        for parent in parents?.all ?? [] {
            for prop in parent.properties ?? [] {
                props.append(prop)
            }
        }
        if props.count == 0 { return nil }
        return props
    }

    // MARK: Codable

    enum CodingKeys: String, CodingKey {
        case discriminator, maxProperties, minProperties, parents, children, discriminatorValue, usage,
            serializationFormats
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        discriminator = try? container.decode(Discriminator.self, forKey: .discriminator)
        maxProperties = try? container.decode(Int.self, forKey: .maxProperties)
        minProperties = try? container.decode(Int.self, forKey: .minProperties)
        parents = try? container.decode(Relations.self, forKey: .parents)
        children = try? container.decode(Relations.self, forKey: .children)
        discriminatorValue = try? container.decode(String.self, forKey: .discriminatorValue)
        usage = try? container.decode([SchemaContext].self, forKey: .usage)
        serializationFormats = try? container.decode([KnownMediaType].self, forKey: .serializationFormats)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if discriminator != nil { try container.encode(discriminator, forKey: .discriminator) }
        if maxProperties != nil { try container.encode(maxProperties, forKey: .maxProperties) }
        if minProperties != nil { try container.encode(minProperties, forKey: .minProperties) }
        if parents != nil { try container.encode(parents, forKey: .parents) }
        if children != nil { try container.encode(children, forKey: .children)
        }
        if discriminatorValue != nil { try container.encode(discriminatorValue, forKey: .discriminatorValue) }
        try container.encode(usage, forKey: .usage)
        try container.encode(serializationFormats, forKey: .serializationFormats)

        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema that represents a ODataQuery value
typealias ODataQuerySchema = Schema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// An operation group represents a container around set of operations
class OperationGroup: Metadata {
    let key: String
    let operations: [Operation]

    enum CodingKeys: String, CodingKey {
        case operations
        case key = "$key"
    }

    var modelName: String {
        return name.isReservedType ? name + "Operation" : name
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        key = try container.decode(String.self, forKey: .key)
        operations = try container.decode([Operation].self, forKey: .operations)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(key, forKey: .key)
        try container.encode(operations, forKey: .operations)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// an OR relationship between several schemas
class OrSchema: ComplexSchema {
    /// the set of schemas that this schema is composed of. Every schema is optional
    let anyOf: [ComplexSchema]

    enum CodingKeys: String, CodingKey {
        case anyOf
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        anyOf = try container.decode([ComplexSchema].self, forKey: .anyOf)
        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(anyOf, forKey: .anyOf)
        try super.encode(to: encoder)
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// A definition of an discrete input for an operation
class Parameter: Value, CustomDebugStringConvertible {
    /// suggested implementation location for this parameter
    let implementation: ImplementationLocation?

    /// When a parameter is flattened, it will be left in the list, but marked hidden (so, don't generate those!)
    let flattened: Bool?

    /// when a parameter is grouped into another, this will tell where the parameter got grouped into
    let groupedBy: Parameter?

    /// Helper to retrieve the HTTP parameter location, if applicable.
    var paramLocation: ParameterLocation? {
        if let httpParam = self.protocol.http as? HttpParameter {
            return httpParam.in
        }
        return nil
    }

    var style: SerializationStyle? {
        if let httpParam = self.protocol.http as? HttpParameter {
            return httpParam.style
        }
        return nil
    }

    var delimiter: String {
        guard let delimiterStyle = style else {
            return ","
        }
        switch delimiterStyle {
        case .pipeDelimited:
            return "|"
        case .spaceDelimited:
            return " "
        case .tabDelimited:
            return "\\t"
        case .form:
            return ","
        default:
            return ","
        }
    }

    // MARK: Codable

    enum CodingKeys: String, CodingKey {
        case implementation, flattened, schema, groupedBy
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        implementation = try? container.decode(ImplementationLocation.self, forKey: .implementation)
        flattened = try? container.decode(Bool.self, forKey: .flattened)
        groupedBy = try? container.decode(Parameter.self, forKey: .groupedBy)

        try super.init(from: decoder)
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if implementation != nil { try container.encode(implementation, forKey: .implementation) }
        if flattened != nil { try container.encode(flattened, forKey: .flattened) }

        try super.encode(to: encoder)
    }

    // MARK: CustomDebugStringConvertible

    public var debugDescription: String {
        return debugString() ?? description
    }

    internal func belongsInSignature(model: CodeModel? = nil) -> Bool {
        // We track body params in a special way, so always omit them generally from the signature.
        // If they belong in the signature, they will be added in a way to ensure they come first.
        guard paramLocation != .body else { return false }

        // Default logic
        let inMethod = implementation == .method
        let notConstant = schema!.type != .constant
        let notGrouped = groupedBy == nil
        let isRequired = required

        var notInGroupSchemaOptions = true
        if let group = model?.schemas.schema(for: name, withType: .group),
            group.name.hasSuffix("Options"),
            paramLocation != .path {
            notInGroupSchemaOptions = false
        }
        return isRequired && inMethod && notConstant && notGrouped && notInGroupSchemaOptions
    }

    internal func belongsInOptions() -> Bool {
        let inMethodImplementation = implementation == .method
        let notConstant = schema!.type != .constant
        let notFlattened = flattened != true
        let notGrouped = groupedBy == nil
        let notRequired = required == false
        let inBodyLocation = paramLocation == .body
        return notRequired && inMethodImplementation && notConstant && notFlattened && notGrouped && !inBodyLocation
    }
}

extension Parameter: Equatable {
    static func == (lhs: Parameter, rhs: Parameter) -> Bool {
        // TODO: Improve this? This is technically the same assumption being
        // made when using a dictionary.
        return lhs.name == rhs.name
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

enum ParameterType: Codable {
    case regular(Parameter)
    case virtual(VirtualParameter)

    // MARK: Codable

    init(from decoder: Decoder) throws {
        if let decoded = try? VirtualParameter(from: decoder) {
            self = .virtual(decoded)
        } else if let decoded = try? Parameter(from: decoder) {
            self = .regular(decoded)
        } else {
            fatalError("Unable to decode ParameterType")
        }
    }

    func encode(to encoder: Encoder) throws {
        switch self {
        case let .regular(param):
            try param.encode(to: encoder)
        case let .virtual(param):
            try param.encode(to: encoder)
        }
    }

    // MARK: Property Helpers

    var name: String {
        return common.name
    }

    var variableName: String {
        return common.variableName
    }

    var schema: Schema {
        return common.schema!
    }

    var value: Value {
        return common as Value
    }

    var required: Bool {
        return common.required
    }

    var description: String {
        return common.description
    }

    var serializedName: String? {
        return common.serializedName
    }

    var `protocol`: Protocols {
        return common.protocol
    }

    var implementation: ImplementationLocation? {
        return common.implementation
    }

    var paramLocation: ParameterLocation? {
        return common.paramLocation
    }

    var style: SerializationStyle? {
        return common.style
    }

    var delimiter: String {
        return common.delimiter
    }

    var clientDefaultValue: String? {
        return common.clientDefaultValue
    }

    var flattened: Bool {
        return common.flattened ?? false
    }

    var groupedBy: Parameter? {
        return common.groupedBy
    }

    var nullable: Bool {
        return common.nullable ?? false
    }

    /// Return the common base class Parameter properties
    private var common: Parameter {
        switch self {
        case let .regular(reg):
            return reg
        case let .virtual(virt):
            return virt as Parameter
        }
    }

    internal var explode: Bool {
        if let httpParam = self.protocol.http as? HttpParameter {
            return httpParam.explode ?? false
        } else {
            return false
        }
    }

    // MARK: Methods

    /// Returns whether the given parameter is located in the specified location.
    internal func located(in location: ParameterLocation) -> Bool {
        if let httpParam = self.protocol.http as? HttpParameter {
            return httpParam.in == location
        } else {
            return false
        }
    }

    /// Returns whether the given parameter is located in any of the specified locations.
    internal func located(in locations: [ParameterLocation]) -> Bool {
        for location in locations {
            if located(in: location) {
                return true
            }
        }
        return false
    }

    internal func belongsInSignature(model: CodeModel? = nil) -> Bool {
        return common.belongsInSignature(model: model)
    }

    internal func belongsInOptions() -> Bool {
        return common.belongsInOptions()
    }
}

extension ParameterType: Equatable {
    static func == (lhs: ParameterType, rhs: ParameterType) -> Bool {
        switch lhs {
        case let .regular(lparam):
            if case let ParameterType.regular(rparam) = rhs {
                return lparam == rparam
            }
        case let .virtual(lparam):
            if case let ParameterType.virtual(rparam) = rhs {
                return lparam == rparam
            }
        }
        return false
    }
}

extension Array where Element == ParameterType {
    func first(named: String) -> Element? {
        return first { $0.name == named }
    }

    /// Returns the subset of `ParameterType` that are `VirtualParameter` types.
    var virtual: [VirtualParameter] {
        return compactMap { param in
            if case let ParameterType.virtual(virtParam) = param {
                return virtParam
            }
            return nil
        }
    }

    /// Returns the required items that should be in the method signature
    func inSignature(model: CodeModel? = nil) -> [ParameterType] {
        return filter { $0.belongsInSignature(model: model) }
    }

    /// Returns the optional items that should be in the method's Options object
    var inOptions: [ParameterType] {
        return filter { $0.belongsInOptions() }
    }

    var inQuery: [ParameterType] {
        return filter { param in
            guard param.implementation == ImplementationLocation.method,
                param.schema.type != .constant
            else { return false }
            return param.located(in: .query)
        }
    }

    /// Returns the items that should be passed in the request header
    var inHeader: [ParameterType] {
        return filter { param in
            guard param.implementation == ImplementationLocation.method,
                param.schema.type != .constant
            else { return false }
            return param.located(in: .header)
        }
    }

    /// Returns the items that should be passed in the path
    var inPath: [ParameterType] {
        return filter { param in
            guard param.implementation == ImplementationLocation.method,
                param.schema.type != .constant
            else { return false }
            return param.located(in: .path)
        }
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

/// a schema types that are primitive language values
typealias PrimitiveSchema = ValueSchema


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

// a property is a child value in an object
class Property: Value {
    // if the property is marked read-only (ie, not intended to be sent to the service)
    let readOnly: Bool?

    // when a property is flattened, the property will be the set of serialized names to  get to that target property.
    // If flattenedName is present, then this property is a flattened property. (ie, ['properties','name'] )
    let flattenedNames: [String]?

    // if this property is used as a discriminator for a polymorphic type
    let isDiscriminator: Bool?

    enum CodingKeys: String, CodingKey {
        case readOnly, serializedName, flattenedNames, isDiscriminator
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.readOnly = try? container.decode(Bool.self, forKey: .readOnly)
        self.flattenedNames = try? container.decode([String].self, forKey: .flattenedNames)
        self.isDiscriminator = try? container.decode(Bool.self, forKey: .isDiscriminator)

        try super.init(from: decoder)
        if let flattened = flattenedNames {
            language.swift.serializedName = flattened.joined(separator: ".")
        } else {
            language.swift.serializedName = try container.decode(String.self, forKey: .serializedName)
        }
    }

    override public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        if readOnly != nil { try container.encode(readOnly, forKey: .readOnly) }
        try container.encode(serializedName, forKey: .serializedName)
        if flattenedNames != nil { try container.encode(flattenedNames, forKey: .flattenedNames) }
        if isDiscriminator != nil { try container.encode(isDiscriminator, forKey: .isDiscriminator) }

        try super.encode(to: encoder)
    }
}

extension Property: Equatable {
    static func == (lhs: Property, rhs: Property) -> Bool {
        // TODO: Improve this? This is technically the same assumption being
        // made when using a dictionary.
        return lhs.name == rhs.name
    }
}


// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------

import Foundation

enum PropertyType: Codable {
    case regular(Property)
    case grouped(GroupProperty)

    // MARK: Codable

    init(from decoder: Decoder) throws {
        if let decoded = try? GroupProperty(from: decoder) {
            self = .grouped(decoded)
        } else if let decoded = try? Property(from: decoder) {
            self = .regular(decoded)
        } else {
            fatalError("Unable to decode PropertyType")
        }
    }

    func encode(to encoder: Encoder) throws {
        switch self {
        case let .regular(param):
            try param.encode(to: encoder)
        case let .grouped(param):
            try param.encode(to: encoder)
        }
    }

    // MARK: Property Helpers

    var name: String {
        return common.name
    }

    var schema: Schema {
        return common.schema!
    }

    var value: Value {
        return common as Value
    }

    var required: Bool {
        return common.required
    }

    var description: String {
        return common.description
    }

    var serializedName: String? {
        return common.serializedName
    }

    var `protocol`: Protocols {
        return common.protocol
    }

    var clientDefaultValue: String? {
        return common.clientDefaultValue
    }

    var readOnly: Bool? {
        return common.readOnly
    }

    var flattenedNames: [String]? {
        return common.flattenedNames
    }

    var isDiscriminator: Bool? {
        return common.isDiscriminator
    }

    private var common: Property {
        switch self {
        case let .regular(reg):
            return reg
        case let .grouped(virt):
            return virt as GroupProperty
        }
    }

    // MARK: Methods

    /// Returns whether the given parameter is located in the specified location.
    internal func located(in location: ParameterLocation) -> Bool {
        if let httpParam = self.protocol.http as? HttpParameter {
            return httpParam.in == location
        } else {
            return false
        }
    }

    /// Returns whether the given parameter is located in any of the specified locations.
    internal func located(in locations: [ParameterLocation]) -> Bool {
        for location in locations {
            if located(in: location) {
                return true
            }
        }
        return false
    }
}

extension PropertyType: Equatable {
    static func == (lhs: PropertyType, rhs: PropertyType) -> Bool {
        switch lhs {
        case let .regular(lparam):
            if case let PropertyType.regular(rparam) = rhs {
                return lparam == rparam
            }
        case let .grouped(lparam):
            if case let PropertyType.grouped(rparam) = rhs {
                return lparam == rparam
            }
        }
        return false
    }
}

extension Array where Element == PropertyType {
    func first(named: String) -> Element? {
        return first { $0.name == named }
    }

    /// Returns the subset of `PropertyType` that are `GroupProperty` types.
    var grouped: [GroupProperty] {
        return compactMap { prop in
            if case let PropertyType.grouped(groupProp) = prop {
                return groupProp
            }
            return nil
        }
    }
}