// --------------------------------------------------------------------------
//
// 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 Discriminator: Codable {
    let property: Property

    let immediate: [String: ComplexSchema]

    let all: [String: ComplexSchema]

    enum CodingKeys: String, CodingKey {
        case property, immediate, all
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        property = try container.decode(Property.self, forKey: .property)
        immediate = try container.decode([String: ComplexSchema].self, forKey: .immediate)
        all = try container.decode([String: ComplexSchema].self, forKey: .all)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(property, forKey: .property)
        try container.encode(immediate, forKey: .immediate)
        try container.encode(all, forKey: .all)
    }
}


// --------------------------------------------------------------------------
//
// 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 reference to external documentation
class ExternalDocumentation: Codable {
    let description: String?

    /// A URI
    let url: String

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

    enum CodingKeys: String, CodingKey {
        case description, url, extensions
    }

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

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


// --------------------------------------------------------------------------
//
// 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 FlagValue: Codable, LanguageShortcut {
    /// per-language information for this value
    public var language: Languages

    /// the actual value
    let value: Int

    /// 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(Int.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)
        try container.encode(language, forKey: .language)
        try container.encode(value, forKey: .value)
        if extensions != nil { try container.encode(extensions, forKey: .extensions) }
    }
}


// --------------------------------------------------------------------------
//
// 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 HttpHeader: Codable {
    let header: String
    let schema: Schema
    let extensions: [String: AnyCodable]?

    enum CodingKeys: String, CodingKey {
        case header, schema, extensions
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        header = try container.decode(String.self, forKey: .header)
        schema = try Schema.decode(withContainer: container)!
        self.extensions = try? container.decode([String: AnyCodable].self, forKey: .extensions)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(header, forKey: .header)
        try container.encode(schema, forKey: .schema)
        try container.encode(extensions, forKey: .extensions)
    }
}


// --------------------------------------------------------------------------
//
// 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 information
class Info: Codable {
    /// the title of this service
    let title: String

    /// a text description of the service
    let description: String?

    /// a URI to the terms of service specified to access the service
    let termsOfService: String?

    /// contact information for the service
    let contact: Contact?

    /// license information for the service
    let license: License?

    /// external documentation
    let externalDocs: ExternalDocumentation?

    /// 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

/// The bare-minimum fields for per-language metadata on a given aspect
class Language: Codable {
    // MARK: Properties

    /// name used in actual implementation
    public var name: String

    /// description text - describes this node.
    public var description: String

    public var summary: String?

    public var serializedName: String?

    public var namespace: String?

    // MARK: AdditionalProperties

    struct PagingNames: Codable {
        static let defaultItemName = "value"

        /// name for the item property
        var itemName: String? = PagingNames.defaultItemName

        /// name of the next link property
        var nextLinkName: String

        /// name of the operation for retrieving the next page
        var operationName: String?

        init?(from dict: [String: String]) {
            guard let nextLinkName = dict["nextLinkName"] else { return nil }
            self.nextLinkName = nextLinkName
            self.itemName = dict["itemName"] ?? PagingNames.defaultItemName
            self.operationName = dict["operationName"]
        }
    }

    public var paging: PagingNames?

    // MARK: Initializers

    public init(from original: Language) {
        self.name = original.name
        self.description = original.description
        self.summary = original.summary
        self.serializedName = original.serializedName
        self.namespace = original.namespace
        self.paging = original.paging
    }
}


// --------------------------------------------------------------------------
//
// 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

/// Custom extensible metadata for individual language generators
class Languages: Codable {
    let `default`: Language

    // these properties we can set
    private var _swift: Language?

    public var swift: Language {
        get {
            if _swift == nil {
                _swift = Language(from: `default`)
            }
            return _swift!
        }
        set {
            _swift = newValue
        }
    }

    public var objectiveC: Language?

    // MARK: Codable

    enum CodingKeys: String, CodingKey {
        case `default`
        case codeSwift = "swift"
        case objectiveC
    }

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

        `default` = try container.decode(Language.self, forKey: .default)
        objectiveC = try? container.decode(Language.self, forKey: .objectiveC)
    }

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

        try container.encode(`default`, forKey: .default)
        if objectiveC != nil { try container.encode(objectiveC, forKey: .objectiveC) }
    }
}


// --------------------------------------------------------------------------
//
// 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

/// License information
class License: Codable {
    /// the name of the license
    let name: String

    /// a URI pointing to the full license text
    let url: String?

    /// 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

/// represents a single callable endpoint with a discrete set of inputs, and any number of output possibilities (responses or exceptions)
class Operation: Codable, LanguageShortcut {
    /// common parameters when there are multiple requests
    let parameters: [ParameterType]?

    /// a common filtered list of parameters that is (assumably) the actual method signature parameters
    let signatureParameters: [ParameterType]?

    /// the different possibilities to build the request.
    let requests: [Request]?

    /// responses that indicate a successful call
    let responses: [Response]?

    /// responses that indicate a failed call
    let exceptions: [Response]?

    /// the apiVersion to use for a given profile name
    let profile: [String: ApiVersion]?

    /// a short description
    let summary: String?

    /// API versions that this applies to. Undefined means all versions
    let apiVersions: [ApiVersion]?

    /// deprecation information -- ie, when this aspect doesn't apply and why
    let deprecated: Deprecation?

    /// where did this aspect come from (jsonpath or 'modelerfour:<something>')
    let origin: String?

    /// External Documentation Links
    let externalDocs: ExternalDocumentation?

    /// 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]?

    /// The request corresponding to this operation
    var request: Request? {
        assert(
            requests?.count ?? 0 <= 1,
            "Multiple requests per operation is currently not supported. Operation: \(name)"
        )
        return requests?.first
    }

    /// Returns the combined list of `ParameterType` objects.
    var allParams: [ParameterType] {
        let paramList = (signatureParameters ?? []) + (parameters ?? []) + (request?.allParams ?? [])
        var params = [ParameterType]()
        for param in paramList {
            if !params.contains(param) {
                params.append(param)
            }
        }
        return params
    }

    var returnsList: Bool {
        for _ in responses ?? [] {}
        return false
    }

    // MARK: Codable

    enum CodingKeys: String, CodingKey {
        case parameters, signatureParameters, requests, responses, exceptions, profile, summary, apiVersions,
            deprecated,
            origin, externalDocs, language, `protocol`, extensions
    }

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

        var responses = [Response]()
        var responsesContainer = try container.nestedUnkeyedContainer(forKey: .responses)
        while !responsesContainer.isAtEnd {
            if let response = (try? responsesContainer.decode(SchemaResponse.self)) ??
                (try? responsesContainer.decode(Response.self)) {
                responses.append(response)
            }
        }
        self.responses = responses

        if var exceptionContainer = try? container.nestedUnkeyedContainer(forKey: .exceptions) {
            var exceptions = [Response]()
            while !exceptionContainer.isAtEnd {
                if let exception = (try? exceptionContainer.decode(SchemaResponse.self)) ??
                    (try? exceptionContainer.decode(Response.self)) {
                    exceptions.append(exception)
                }
            }
            self.exceptions = exceptions
        } else {
            self.exceptions = nil
        }

        self.parameters = try? container.decode([ParameterType].self, forKey: .parameters)
        self.signatureParameters = try? container.decode([ParameterType].self, forKey: .signatureParameters)
        self.requests = try? container.decode([Request].self, forKey: .requests)
        self.profile = try? container.decode([String: ApiVersion].self, forKey: .profile)
        self.summary = try? container.decode(String.self, forKey: .summary)
        self.apiVersions = try? container.decode([ApiVersion].self, forKey: .apiVersions)
        self.deprecated = try? container.decode(Deprecation.self, forKey: .deprecated)
        self.origin = try? container.decode(String.self, forKey: .origin)
        self.externalDocs = try? container.decode(ExternalDocumentation.self, forKey: .externalDocs)
        self.extensions = try? container.decode([String: AnyCodable].self, forKey: .extensions)
        self.language = try container.decode(Languages.self, forKey: .language)
        self.protocol = try container.decode(Protocols.self, forKey: .protocol)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(parameters, forKey: .parameters)
        try container.encode(signatureParameters, forKey: .signatureParameters)
        try container.encode(requests, forKey: .requests)
        try container.encode(responses, forKey: .responses)
        if exceptions != nil { try container.encode(exceptions, forKey: .exceptions) }
        if profile != nil { try container.encode(profile, forKey: .profile) }
        if summary != nil { try container.encode(summary, forKey: .summary) }
        try container.encode(apiVersions, forKey: .apiVersions)
        if deprecated != nil { try container.encode(deprecated, forKey: .deprecated) }
        if origin != nil { try container.encode(origin, forKey: .origin) }
        if externalDocs != nil { try container.encode(externalDocs, forKey: .externalDocs) }
        try container.encode(language, forKey: .language)
        try container.encode(`protocol`, forKey: .protocol)
        if extensions != nil { try container.encode(extensions, forKey: .extensions) }
    }

    /// Lookup a signatureParameter by name.
    func signatureParameter(for name: String) -> ParameterType? {
        return signatureParameters?.first(named: name)
    }

    /// Lookup a parameter by name.
    func parameter(for name: String) -> ParameterType? {
        return parameters?.first(named: 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

/// Custom extensible metadata for individual protocols (ie, HTTP, etc)
class Protocols: Codable {
    let http: ProtocolInterface?

    let amqp: ProtocolInterface?

    let mqtt: ProtocolInterface?

    let jsonrpc: ProtocolInterface?

    enum CodingKeys: String, CodingKey {
        case http, amqp, mqtt, jsonrpc
    }

    // MARK: Codable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        http = (try? container.decode(HttpWithBodyRequest.self, forKey: .http)) ??
            (try? container.decode(HttpRequest.self, forKey: .http)) ??
            (try? container.decode(HttpParameter.self, forKey: .http)) ??
            (try? container.decode(HttpResponse.self, forKey: .http)) ??
            (try? container.decode(HttpModel.self, forKey: .http))
        // TODO: Finish implementation
        amqp = nil
        self.mqtt = nil
        self.jsonrpc = nil
    }

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

        if http is HttpWithBodyRequest {
            try container.encode(http as? HttpWithBodyRequest, forKey: .http)
        } else if http is HttpParameter {
            try container.encode(http as? HttpParameter, forKey: .http)
        } else if http is HttpResponse {
            try container.encode(http as? HttpResponse, forKey: .http)
        } else if http is HttpModel {
            try container.encode(http as? HttpModel, forKey: .http)
        } else if http is HttpRequest {
            try container.encode(http as? HttpRequest, forKey: .http)
        }

        // TODO: Finish implementation
    }
}


// --------------------------------------------------------------------------
//
// 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 Relations: Codable {
    let immediate: [ComplexSchema]

    let all: [ComplexSchema]
}


// --------------------------------------------------------------------------
//
// 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
// swiftlint:disable cyclomatic_complexity

/// The full set of schemas for a given service, categorized into convenient collections.
class Schemas: Codable {
    /// a collection of items
    let arrays: [ArraySchema]?

    /// an associative array (ie, dictionary, hashtable, etc)
    let dictionaries: [DictionarySchema]?

    /// a true or false value
    let booleans: [BooleanSchema]?

    /// a number value
    let numbers: [NumberSchema]?

    /// an object of some type
    let objects: [ObjectSchema]?

    /// a string of characters
    let strings: [StringSchema]?

    /// UnixTime
    let unixTimes: [UnixTimeSchema]?

    /// an array of bytes
    let byteArrays: [ByteArraySchema]?

    let streams: [Schema]?

    /// a single characters
    let chars: [CharSchema]?

    /// a date
    let dates: [DateSchema]?

    /// a time
    let times: [TimeSchema]?

    /// a datetime
    let dateTimes: [DateTimeSchema]?

    /// a duration
    let durations: [DurationSchema]?

    /// a universally unique identifier
    let uuids: [UuidSchema]?

    /// a URI of some kind
    let uris: [UriSchema]?

    /// a password or credential
    let credentials: [CredentialSchema]?

    /// OData query
    let odataQueries: [ODataQuerySchema]?

    /// this essentially can be thought of as an enum that is a choice between one of several items, but an unspecified value is permitted
    let choices: [ChoiceSchema]?

    /// this essentially can be thought of as an enum that is a choice between one of several items, but an unknown value is not allowed
    let sealedChoices: [SealedChoiceSchema]?

    /// ie, when 'profile' is 'production', use '2018-01-01' for apiversion
    let conditionals: [ConditionalSchema]?

    let sealedConditionals: [SealedConditionalSchema]?

    let flags: [FlagSchema]?

    /// a constant value
    let constants: [ConstantSchema]?

    let ors: [OrSchema]?

    let xors: [XorSchema]?

    let binaries: [BinarySchema]?

    /// it's possible that we just may make this an error \nin representation.
    let unknowns: [Schema]?

    let groups: [GroupSchema]?

    let any: [AnySchema]?

    enum CodingKeys: String, CodingKey {
        case arrays
        case dictionaries
        case booleans
        case numbers
        case objects
        case strings
        case unixTimes = "unixtimes"
        case byteArrays
        case streams
        case chars
        case dates
        case times
        case dateTimes
        case durations
        case uuids
        case uris
        case credentials
        case odataQueries
        case choices
        case sealedChoices
        case conditionals
        case sealedConditionals
        case flags
        case constants
        case ors
        case xors
        case binaries
        case unknowns
        case groups
        case any
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        arrays = try? container.decode([ArraySchema].self, forKey: .arrays)
        dictionaries = try? container.decode([DictionarySchema].self, forKey: .dictionaries)
        booleans = try? container.decode([BooleanSchema].self, forKey: .booleans)
        numbers = try? container.decode([NumberSchema].self, forKey: .numbers)
        objects = try? container.decode([ObjectSchema].self, forKey: .objects)
        strings = try? container.decode([StringSchema].self, forKey: .strings)
        unixTimes = try? container.decode([UnixTimeSchema].self, forKey: .unixTimes)
        byteArrays = try? container.decode([ByteArraySchema].self, forKey: .byteArrays)
        streams = try? container.decode([Schema].self, forKey: .streams)
        chars = try? container.decode([CharSchema].self, forKey: .chars)
        dates = try? container.decode([DateSchema].self, forKey: .dates)
        times = try? container.decode([TimeSchema].self, forKey: .times)
        dateTimes = try? container.decode([DateTimeSchema].self, forKey: .dateTimes)
        durations = try? container.decode([DurationSchema].self, forKey: .durations)
        uuids = try? container.decode([UuidSchema].self, forKey: .uuids)
        uris = try? container.decode([UriSchema].self, forKey: .uris)
        credentials = try? container.decode([CredentialSchema]?.self, forKey: .credentials)
        odataQueries = try? container.decode([ODataQuerySchema].self, forKey: .odataQueries)
        choices = try? container.decode([ChoiceSchema].self, forKey: .choices)
        sealedChoices = try? container.decode([SealedChoiceSchema].self, forKey: .sealedChoices)
        conditionals = try? container.decode([ConditionalSchema].self, forKey: .conditionals)
        sealedConditionals = try? container.decode([SealedConditionalSchema].self, forKey: .sealedConditionals)
        flags = try? container.decode([FlagSchema].self, forKey: .flags)
        constants = try? container.decode([ConstantSchema].self, forKey: .constants)
        ors = try? container.decode([OrSchema].self, forKey: .ors)
        xors = try? container.decode([XorSchema].self, forKey: .xors)
        binaries = try? container.decode([BinarySchema]?.self, forKey: .binaries)
        unknowns = try? container.decode([Schema].self, forKey: .unknowns)
        groups = try? container.decode([GroupSchema].self, forKey: .groups)
        any = try? container.decode([AnySchema].self, forKey: .any)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if arrays != nil { try? container.encode(arrays, forKey: .arrays) }
        if dictionaries != nil { try? container.encode(dictionaries, forKey: .dictionaries) }
        if booleans != nil { try? container.encode(booleans, forKey: .booleans) }
        if numbers != nil { try? container.encode(numbers, forKey: .numbers) }
        if objects != nil { try? container.encode(objects, forKey: .objects) }
        if strings != nil { try? container.encode(strings, forKey: .strings) }
        if unixTimes != nil { try? container.encode(unixTimes, forKey: .unixTimes) }
        if byteArrays != nil { try? container.encode(byteArrays, forKey: .byteArrays) }
        if streams != nil { try? container.encode(streams, forKey: .streams) }
        if chars != nil { try? container.encode(chars, forKey: .chars) }
        if dates != nil { try? container.encode(dates, forKey: .dates) }
        if times != nil { try? container.encode(times, forKey: .times) }
        if dateTimes != nil { try? container.encode(dateTimes, forKey: .dateTimes) }
        if durations != nil { try? container.encode(durations, forKey: .durations) }
        if uuids != nil { try? container.encode(uuids, forKey: .uuids) }
        if uris != nil { try? container.encode(uris, forKey: .uris) }
        if credentials != nil { try? container.encode(credentials, forKey: .credentials) }
        if odataQueries != nil { try? container.encode(odataQueries, forKey: .odataQueries) }
        if choices != nil { try? container.encode(choices, forKey: .choices) }
        if sealedChoices != nil { try? container.encode(sealedChoices, forKey: .sealedChoices) }
        if conditionals != nil { try? container.encode(conditionals, forKey: .conditionals) }
        if sealedConditionals != nil { try? container.encode(sealedConditionals, forKey: .sealedConditionals) }
        if flags != nil { try? container.encode(flags, forKey: .flags) }
        if constants != nil { try? container.encode(constants, forKey: .constants) }
        if ors != nil { try? container.encode(ors, forKey: .ors) }
        if xors != nil { try? container.encode(xors, forKey: .xors) }
        if binaries != nil { try? container.encode(binaries, forKey: .binaries) }
        if unknowns != nil { try? container.encode(unknowns, forKey: .unknowns) }
        if groups != nil { try? container.encode(groups, forKey: .groups) }
        if any != nil { try? container.encode(any, forKey: .any) }
    }

    /// Lookup a schema by name.
    func schema(for name: String, withType type: AllSchemaTypes) -> Schema? {
        switch type {
        case .string:
            return strings?.first { $0.name == name }
        case .integer:
            return numbers?.first { $0.name == name }
        case .group:
            return groups?.first { $0.name(equals: name) }
        default:
            fatalError("Unhandled schema type: \(type)")
        }
    }
}


// --------------------------------------------------------------------------
//
// 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

/// The security information for the API surface
class Security: Codable {
    /// indicates that the API surface requires authentication
    let authenticationRequired: 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

/// common ways of serializing simple parameters
class SecurityRequirement: Codable {}


// --------------------------------------------------------------------------
//
// 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

/// Custom extensible metadata for individual serialization formats
class SerializationFormats: Codable {
    let json: SerializationFormat?

    let xml: XmlSerializationFormat?

    let protobuf: SerializationFormat?

    let binary: SerializationFormat?

    enum CodingKeys: String, CodingKey {
        case json, xml, protobuf, binary
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        json = try? container.decode(SerializationFormat?.self, forKey: .json)
        xml = try? container.decode(XmlSerializationFormat?.self, forKey: .xml)
        protobuf = try? container.decode(SerializationFormat?.self, forKey: .protobuf)
        binary = try? container.decode(SerializationFormat?.self, forKey: .binary)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if json != nil { try? container.encode(json, forKey: .json) }
        if xml != nil { try? container.encode(xml, forKey: .xml) }
        if protobuf != nil { try? container.encode(protobuf, forKey: .protobuf) }
        if binary != nil { try? container.encode(binary, forKey: .binary) }
    }
}


// --------------------------------------------------------------------------
//
// 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

extension Encodable {
    func prettyPrint() {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        if let jsonData = try? encoder.encode(self) {
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                print(jsonString)
            }
        }
    }

    func debugString() -> String? {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        if let jsonData = try? encoder.encode(self) {
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                return jsonString
            }
        }
        return 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.
//
// --------------------------------------------------------------------------

import Foundation

private let reservedTypes: Set = [
    "Error",
    "Int",
    "String",
    "Bool",
    "Enum",
    "Array",
    "Date",
    "Self",
    "Any"
]

private let reservedKeywords: Set = [
    "associatedtype",
    "class",
    "deinit",
    "enum",
    "extension",
    "fileprivate",
    "func",
    "import",
    "init",
    "inout",
    "internal",
    "let",
    "open",
    "operator",
    "private",
    "protocol",
    "public",
    "static",
    "struct",
    "subscript",
    "typealias",
    "var",
    "break",
    "case",
    "continue",
    "default",
    "defer",
    "do",
    "else",
    "fallthrough",
    "for",
    "guard",
    "if",
    "in",
    "repeat",
    "return",
    "switch",
    "where",
    "while",
    "as",
    "catch",
    "false",
    "is",
    "nil",
    "rethrows",
    "super",
    "self",
    "throw",
    "throws",
    "true",
    "try"
]

extension String {
    /// Returns `true` if the string corresponds to a reserved type
    var isReservedType: Bool {
        return reservedTypes.contains(self)
    }

    /// Returns `true` if the string corresponds to a reserved Swift keyword
    var isReservedKeyword: Bool {
        return reservedKeywords.contains(self)
    }
}


// --------------------------------------------------------------------------
//
// 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

import Stencil

public func renderTemplate(filename: String, dictionary: [String: Any]) throws -> String {
    let fsLoader = FileSystemLoader(bundle: [Bundle.main])
    let environment = Environment(loader: fsLoader, trimBehavior: TrimBehavior.smart)

    return try environment.renderTemplate(name: filename, context: dictionary)
}


// --------------------------------------------------------------------------
//
// 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

extension String {
    /// Returns a list of lowercased string components split on uppercase characters with
    /// acryonyms joined
    var splitAndJoinAcronyms: [String] {
        let value = replacingOccurrences(
            of: "([A-Z])",
            with: " $1",
            options: .regularExpression,
            range: range(of: self)
        )
        .trimmingCharacters(in: .whitespacesAndNewlines)
        .lowercased()

        let fineComponents = value.split(separator: " ").map { String($0) }
        var components = [String]()
        var index = 0

        // search for any acronyms that may be hidden in the string
        while index < fineComponents.count {
            let comp = fineComponents[index]
            if comp.count > 1 {
                components.append(comp)
                index += 1
            } else {
                // combine an acronym to a single component
                var acronym = comp
                var endex = index + 1
                while endex < fineComponents.count {
                    let curr = fineComponents[endex]
                    if curr.count == 1 {
                        acronym += curr
                        endex += 1
                        index += 1
                        continue
                    }
                    components.append(acronym)
                    index += 1
                    acronym = ""
                    break
                }
                if acronym != "" {
                    components.append(acronym)
                    index += 1
                    acronym = ""
                }
            }
        }
        return components
    }

    /// Returns the substring without any trailing or leading whitespace.
    func trimmed() -> Substring {
        guard let firstElementIndex = firstIndex(where: { !$0.isWhitespace }) else {
            return Substring("")
        }

        let lastElementIndex = reversed().firstIndex(where: { !$0.isWhitespace })!
        return self[firstElementIndex ..< lastElementIndex.base]
    }

    /// Returns the string with the first character lowercased.
    var lowercasedFirst: String { return prefix(1).lowercased() + dropFirst() }

    /// Returns the string with the first character uppercased.
    var uppercasedFirst: String { return prefix(1).uppercased() + dropFirst() }
}


// --------------------------------------------------------------------------
//
// 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

extension URL {
    func ensureExists() throws {
        let fileManager = FileManager.default

        if let existing = try? resourceValues(forKeys: [.isDirectoryKey]) {
            if !existing.isDirectory! {
                let err = "Path exists but is not a folder!"
                fatalError(err)
            }
        } else {
            // Path does not exist so let us create it
            try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
        }
    }

    func with(subfolder: FileDestination) -> URL {
        return subfolder.url(forBaseUrl: self)
    }

    func append(line: String) throws {
        guard let lineData = "\(line)\n".data(using: .utf8) else { return }
        if let handle = FileHandle(forWritingAtPath: path) {
            defer { handle.closeFile() }
            handle.seekToEndOfFile()
            handle.write(lineData)
        } else {
            try lineData.write(to: self, options: .atomic)
        }
    }
}


// --------------------------------------------------------------------------
//
// 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 BodyParamStrategy: String {
    case plain
    case plainNullable
    case flattened
    case unixTime
    case byteArray
    case constant
    case decimal
    case data
    case string
    case time
}

struct BodyParams {
    /// The `ParameterViewModel` corresponding to the body param
    var param: ParameterViewModel

    /// Identifies the correct snippet to use when rendering the view model
    let strategy: String

    /// A list of `VirtualParam` that correlate to a flattened body parmeter
    let children: [VirtualParam]

    /// Returns `true` if the body parameter is flattened
    var flattened: Bool {
        return !children.isEmpty
    }

    init(from param: ParameterType, parameters: [ParameterType]) {
        self.param = ParameterViewModel(from: param)
        var properties = param.schema.properties
        if let objectSchema = param.schema as? ObjectSchema {
            properties = objectSchema.flattenedProperties
        }
        var virtParams = [VirtualParam]()
        for child in properties ?? [] {
            guard child.schema as? ConstantSchema == nil else { continue }
            if let virtParam = parameters.virtual.first(where: { $0.name == child.name }) {
                virtParams.append(VirtualParam(from: virtParam))
            }
        }
        var strategy: BodyParamStrategy = .plain

        if param.flattened {
            strategy = .flattened
        } else if param.nullable {
            strategy = .plainNullable
        } else if param.schema is ConstantSchema {
            strategy = .constant
        } else {
            strategy = param.schema.bodyParamStrategy
            if strategy == .plain, !param.required {
                strategy = .plainNullable
            }
        }
        self.strategy = strategy.rawValue
        self.children = virtParams
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for the body param.
struct BodyParamViewModel {
    let name: String
    let type: String
    let flattened: Bool
    let properties: [ParameterViewModel]

    init(from parameter: ParameterType, with operation: Operation) {
        self.name = operation.request?.bodyParamName(for: operation) ?? parameter.name
        self.type = parameter.schema.name
        self.flattened = parameter.flattened
        self.properties = [ParameterViewModel]()
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for an individual client method Options object.
struct ClientMethodOptionsViewModel {
    /// The name of the client class (i.e. AzureStorageBlob)
    let clientName: String

    /// The name of the operation (i.e. listContainers)
    let operationName: String

    /// The name of the options object (i.e. ListContainersOptions)
    let name: String

    // The list of properites for the options object
    let properties: [PropertyViewModel]

    let extensionName: String

    let visibility: String

    init(from operation: Operation, with model: CodeModel, parameters: [ParameterType], groupName: String) {
        self.clientName = model.name
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: clientName)
        self.operationName = operation.name
        self.name = "\(operation.name)Options"
        if model.object(for: groupName) != nil {
            self.extensionName = groupName + "Operation"
        } else {
            self.extensionName = groupName
        }
        var properties = [PropertyViewModel]()

        // In generated code stencil, there is already a 'clientRequestId', so do not duplicate by adding anyone with same name
        // TODO: Revisit how to handle local swagger overrides predefined properties in Autorest swift generated code
        for parameter in parameters where parameter.value.name != "clientRequestId" {
            properties.append(PropertyViewModel(from: parameter.value))
        }

        // For `Options` Group schema created by "x-ms-parameter-grouping" with postfix: Options,
        // add all the properties from that group schema except property in path as the properties of the Method Option object
        if let group = model.schemas.schema(for: groupName + name, withType: .group),
            group.name.hasSuffix("Options") {
            for property in group.properties ?? [] {
                switch property {
                case let .grouped(groupProperty):
                    for param in groupProperty.originalParameter where param.paramLocation != .path {
                        properties.append(PropertyViewModel(from: property.value))
                    }
                default:
                    continue
                }
            }
        }

        self.properties = properties
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for the service client file.
struct ClientOptionsFileViewModel {
    let name: String
    let packageName: String
    let visibility: String
    let apiVersion: String
    let apiVersionName: String

    init(from model: CodeModel) {
        let baseName = "\(model.packageName)Client"
        self.name = Manager.shared.args!.generateAsInternal.aliasOrName(for: "\(baseName)Options")
        self.packageName = model.packageName
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: name)
        self.apiVersion = model.getApiVersion()
        /// Swift enums should contain only alphanumeric characters.
        self
            .apiVersionName =
            "v\(apiVersion.replacingOccurrences(of: "-", with: "").replacingOccurrences(of: ".", with: ""))"
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for a constant model property.
/// Example:
///     // a constant property
///     let constant = "default"
struct ConstantViewModel {
    let name: String
    let comment: ViewModelComment
    // default value of the proeprty
    let defaultValue: ViewModelDefault
    let serializedName: String

    /// Initialize from Property
    init(from property: Property) {
        guard let constSchema = property.schema as? ConstantSchema else { fatalError("Expected constant schema.") }
        let name = property.name
        assert(!name.isEmpty)
        self.name = name
        self.serializedName = property.serializedName ?? property.name
        self.comment = ViewModelComment(from: property.description)
        self.defaultValue = ViewModelDefault(
            from: constSchema.value.value,
            isString: constSchema.valueType is StringSchema,
            isOptional: false
        )
    }

    init(from schema: PropertyType) {
        switch schema {
        case let .regular(reg):
            self.init(from: reg)
        case let .grouped(group):
            self.init(from: group)
        }
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for an enumeration choice.
/// Example:
///     // a simple choice
///     case simple = "Simple"
struct EnumerationChoiceViewModel {
    let name: String
    let comment: ViewModelComment
    let value: String

    init(from schema: ChoiceValue) {
        // Model4 passed in ChoiceValue name with first letter in Upper case despite the setting
        // for ChoiceValue is set to `camelcase` in README.md
        // Enum value starts with an Upper case will cause swiftlint error and swiftlint autocorrect will not fix this issue.
        // As a workaround, we lower caes the first letter of ChoiceValue in the view model.
        self.name = schema.variableName
        self.comment = ViewModelComment(from: schema.description)
        self.value = schema.value
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for the Enumerations.swift file.
struct EnumerationFileViewModel {
    let enums: [EnumerationViewModel]

    init(from schema: Schemas) {
        var items = [EnumerationViewModel]()
        for choice in schema.choices ?? [] {
            items.append(EnumerationViewModel(from: choice))
        }
        for choice in schema.sealedChoices ?? [] {
            items.append(EnumerationViewModel(from: choice))
        }
        self.enums = items
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for an enumeration object.
/// Example:
///     // a simple enum
///     public enum SimpleEnum: String, Codable { ... }
struct EnumerationViewModel {
    let name: String
    let comment: ViewModelComment
    let visibility: String
    let type: String
    let choices: [EnumerationChoiceViewModel]
    let isExtensible: Bool

    init(from schema: EnumerableSchema) {
        self.name = schema.name
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: schema.name)
        self.comment = ViewModelComment(from: schema.description)

        self.type = schema.choiceType.name
        var items = [EnumerationChoiceViewModel]()
        for choice in schema.choices {
            items.append(EnumerationChoiceViewModel(from: choice))
        }
        self.choices = items
        // extensible enums are those with `modelAsString = true` in Swagger.
        // This translates to a sealedChoice type in the code model.
        self.isExtensible = schema as? SealedChoiceSchema == 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.
//
// --------------------------------------------------------------------------

import Foundation

/// View Model for method exception response handling.
struct ExceptionResponseViewModel {
    /// List of allowed response status codes
    let statusCodes: [String]

    /// Swift type annotation, including optionality, if applicable
    let type: String

    /// The comment to use for the return type
    let description: String?

    /// Indicates if the operation has a default exception
    let hasDefaultException: Bool

    /// Identifies the correct snippet to use when rendering the view model
    let strategy: String

    init(from response: Response) {
        let httpResponse = response.protocol.http as? HttpResponse
        var statusCodes = [String]()
        httpResponse?.statusCodes.forEach { statusCodes.append($0.rawValue) }

        self.statusCodes = statusCodes.filter { $0 != "default" }.sorted()

        // check if the request body schema type is object, store the object type of the response body
        let schemaResponse = response as? SchemaResponse
        self.description = schemaResponse?.description

        if let type = schemaResponse?.schema.swiftType(),
            let schema = schemaResponse?.schema {
            self.strategy = ResponseBodyType.strategy(for: type, and: schema).rawValue
            self.type = type
        } else {
            self.strategy = ResponseBodyType.noBody.rawValue
            self.type = "Void"
        }

        if let errorResponseMetadata = response.extensions?["x-ms-error-response"]?.value as? Bool,
            errorResponseMetadata {
            guard type != "Void"
            else { fatalError("Did not find object type for error response") }
        }
        self.hasDefaultException = statusCodes.contains("default")
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for a class or struct defintion.
/// Example:
///   // a simple model object
///   public struct ModelObject { ... }
struct ObjectViewModel {
    let name: String
    let comment: ViewModelComment
    let properties: [PropertyViewModel]
    let constants: [ConstantViewModel]
    let hasConstants: Bool
    let visibility: String

    var inheritance = "NSObject"
    var typeKeywords = "struct"
    var isErrorType = false

    init(from schema: ObjectSchema) {
        self.name = schema.modelName
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: schema.modelName)
        self.comment = ViewModelComment(from: schema.description)

        // flatten out inheritance hierarchies so we can use structs
        var props = [PropertyViewModel]()
        var consts = [ConstantViewModel]()
        for property in schema.flattenedProperties ?? [] {
            if property.schema as? ConstantSchema != nil {
                consts.append(ConstantViewModel(from: property))
            } else {
                // properties should inherit the visibility of their parent
                props.append(PropertyViewModel(from: property, parentName: name))
            }
        }
        self.properties = props
        self.constants = consts
        self.hasConstants = !consts.isEmpty

        checkForErrorType(with: schema)
        checkForCircularReferences()
    }

    private mutating func checkForCircularReferences() {
        // a struct cannot contain a circular reference, so these must be class types
        for property in properties {
            // remove any ? optionality
            var type = property.type
            if type.hasSuffix("?") {
                type.removeLast()
            }
            type = type.trimmingCharacters(in: .whitespacesAndNewlines)
            let key = name.trimmingCharacters(in: .whitespacesAndNewlines)
            if key == type {
                typeKeywords = "final class"
                return
            }
        }
    }

    init(from schema: GroupSchema) {
        self.name = schema.modelName
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: schema.modelName)
        self.comment = ViewModelComment(from: schema.description)

        // flatten out inheritance hierarchies so we can use structs
        var props = [PropertyViewModel]()
        var consts = [ConstantViewModel]()
        let groupedProperties = schema.properties?.grouped ?? []
        assert(
            groupedProperties.count == (schema.properties?.count ?? 0),
            "Expected all properties to be group properties."
        )
        for property in groupedProperties {
            // the source of flattened parameters should not be included in the view model

            // FIXME: This assumption no longer holds for storage and should be revisited
            // assert(property.originalParameter.count <= 1, "Expected, at most, one original parameter.")
            if property.originalParameter.first?.flattened ?? false {
                continue
            }
            if property.schema as? ConstantSchema != nil {
                consts.append(ConstantViewModel(from: property))
            } else {
                props.append(PropertyViewModel(from: property))
            }
        }
        self.properties = props
        self.constants = consts
        self.hasConstants = !consts.isEmpty

        checkForErrorType(with: schema)
        checkForCircularReferences()
    }

    private mutating func checkForErrorType(with schema: UsageSchema) {
        // FIXME: What if there is more than 1 usage??
        var isErrorType = false
        if let usage = schema.usage,
            usage.count > 0 {
            isErrorType = usage.first == SchemaContext.exception
        }
        self.isErrorType = isErrorType
        let parents = isErrorType ? ["Codable", "Swift.Error"] : ["Codable"]
        inheritance = parents.joined(separator: ", ")
    }
}


// --------------------------------------------------------------------------
//
// 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

/// View Model for an operation group
/// Example:
///     // MARK: OperationGroupName
///     ...
struct OperationGroupViewModel {
    let name: String
    let visibility: String
    let operations: [OperationViewModel]
    let comment: ViewModelComment

    init(from group: OperationGroup, with model: CodeModel) {
        let name = group.name.isEmpty ? model.name : group.modelName

        var groupName = name.trimmingCharacters(in: .whitespacesAndNewlines)
        // if the operation group name has a collision with a model object, append 'Operation' to the operation group name
        // or group name is a reserved keyword
        if model.object(for: groupName) != nil {
            groupName += "Operation"
        }

        // Operation group visibility should match that of the client
        let clientName = Manager.shared.args!.generateAsInternal.aliasOrName(for: "\(model.packageName)Client")
        self.visibility = Manager.shared.args!.generateAsInternal.visibility(for: clientName)

        self.name = groupName
        self.comment = ViewModelComment(from: group.description)
        var items = [OperationViewModel]()
        for operation in group.operations {
            items.append(OperationViewModel(from: operation, with: model, groupName: groupName))
        }
        self.operations = items
    }
}


// --------------------------------------------------------------------------
//
// 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

struct OperationParameters {
    let all: [ParameterViewModel]
    let signature: [ParameterViewModel]
    let explode: [ParameterViewModel]
    var body: BodyParams?
    var declaration: String

    /// Initialize with a list of `ParameterType`.
    init(parameters: [ParameterType], operation: Operation, model: CodeModel) {
        var params = [ParameterViewModel]()
        var explodeParams = [ParameterViewModel]()

        for param in parameters {
            guard let paramLocation = param.paramLocation else { continue }
            let viewModel = ParameterViewModel(from: param, with: operation)

            switch paramLocation {
            case .query:
                if param.explode {
                    explodeParams.append(viewModel)
                } else {
                    params.append(viewModel)
                }
            case .header, .path, .uri:
                params.append(viewModel)
            case .body:
                // TODO: Body params are handled differently and shouldn't go into RequestParameters at this time
                // We may be able to refactor and change this.
                continue
            default:
                continue
            }
        }

        // Set the body param, if applicable
        var bodyParamName: String?
        let bodyParams = params.filter { $0.location == "body" }
        assert(bodyParams.count <= 1, "Expected, at most, one body parameter.")
        if bodyParams.count > 0 {
            bodyParamName = bodyParams.first?.name
        } else {
            bodyParamName = operation.request?.bodyParamName(for: operation)
        }
        if let bodyParam = operation.request?.bodyParam {
            self.body = BodyParams(
                from: bodyParam,
                parameters: parameters
            )
            // update the body param name to fit Swift conventions
            body?.param.name = bodyParamName ?? bodyParam.name
        } else {
            self.body = nil
        }

        var signatureViewModel = [ParameterViewModel]()
        for param in parameters {
            // For `Options` Group schema created by "x-ms-parameter-grouping" with postfix: Options,
            // look for  all the properties from that group schema which is in 'path' as the signature of the method
            if let group = model.schemas.schema(for: param.name, withType: .group) as? GroupSchema,
                group.name.hasSuffix("Options") {
                for property in group.properties ?? [] {
                    switch property {
                    case let .grouped(groupProperty):
                        for param in groupProperty.originalParameter where param.paramLocation == .path {
                            signatureViewModel.append(ParameterViewModel(from: param))
                        }
                    default:
                        continue
                    }
                }
            } else if param.belongsInSignature(model: model) {
                signatureViewModel.append(ParameterViewModel(from: param))
            }
        }
        self.all = params
        self.signature = signatureViewModel
        self.explode = explodeParams
        self.declaration = explode.isEmpty ? "let" : "var"
    }
}