//
//  ConcurrentMutableSetTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class ConcurrentMutableSetTests: XCTestCase {
    private var set: ConcurrentMutableSet!

    override func setUp() {
        super.setUp()
        set = ConcurrentMutableSet()
    }

    func testPutAndContainsShouldWork() {
        set.add(1)
        XCTAssertTrue(set.contains(1))
    }

    func testRemoveShouldWork() {
        set.add(1)
        XCTAssertTrue(set.contains(1))

        set.remove(1)
        XCTAssertFalse(set.contains(1))
    }

    func testForEachShouldWork() {
        set.add(1)
        set.add(10)
        set.add(100)

        var sum: Int = 0
        var count: Int = 0
        set.forEach { item in
            guard let value = item as? Int else { return }
            count += 1
            sum += value
        }

        XCTAssertEqual(sum, 111)
        XCTAssertEqual(count, 3)
    }

    func testThreadSafety() {
        set.add(1)
        set.add(10)
        var count = 0
        var sum = 0
        let backgroundThreadEndedExpectation = XCTestExpectation(
            description: "The background thread was ended")
        let mainThreadEndedExpectation = XCTestExpectation(
            description: "The main thread was ended")

        DispatchQueue.global(qos: .userInteractive).async {
            self.set.forEach { item in
                sleep(2)
                guard let value = item as? Int, self.set.contains(item) else { return }
                count += 1
                sum += value
            }
            backgroundThreadEndedExpectation.fulfill()
        }
        DispatchQueue.main.async {
            sleep(1)
            self.set.remove(10)
            mainThreadEndedExpectation.fulfill()
        }

        wait(for: [backgroundThreadEndedExpectation, mainThreadEndedExpectation], timeout: 5)
        XCTAssertEqual(count, 2)
        XCTAssertEqual(sum, 11)
        XCTAssertEqual(set.count, 1)
        XCTAssert(set.contains(1))
    }

    func testThreadSafetyShouldFailForNormalSet() {
        let normalSet = NSMutableSet()
        normalSet.add(1)
        normalSet.add(10)
        var count = 0
        var sum = 0
        let backgroundThreadEndedExpectation = XCTestExpectation(
            description: "The background thread was ended")
        let mainThreadEndedExpectation = XCTestExpectation(
            description: "The main thread was ended")

        DispatchQueue.global(qos: .background).async {
            normalSet.forEach { item in
                sleep(2)
                guard let value = item as? Int, normalSet.contains(item) else { return }
                count += 1
                sum += value
            }
            backgroundThreadEndedExpectation.fulfill()
        }
        DispatchQueue.main.async {
            sleep(1)
            normalSet.remove(10)
            mainThreadEndedExpectation.fulfill()
        }

        wait(for: [backgroundThreadEndedExpectation, mainThreadEndedExpectation], timeout: 7)
        XCTAssertEqual(count, 1)
        XCTAssertEqual(sum, 1)
        XCTAssertEqual(normalSet.count, 1)
        XCTAssert(normalSet.contains(1))
    }
}


//
//  ConvertersTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import XCTest

class ConvertersTests: XCTestCase {
    func testAudioClientStatusToMeetingSessionStatusCode() {
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_OK),
            MeetingSessionStatusCode.ok
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_SERVER_HUNGUP),
            MeetingSessionStatusCode.audioServerHungup
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                status: AUDIO_CLIENT_ERR_JOINED_FROM_ANOTHER_DEVICE
            ),
            MeetingSessionStatusCode.audioJoinedFromAnotherDevice
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_INTERNAL_SERVER_ERROR),
            MeetingSessionStatusCode.audioInternalServerError
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_AUTH_REJECTED),
            MeetingSessionStatusCode.audioAuthenticationRejected
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_CALL_AT_CAPACITY),
            MeetingSessionStatusCode.audioCallAtCapacity
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_SERVICE_UNAVAILABLE),
            MeetingSessionStatusCode.audioServiceUnavailable
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_SHOULD_DISCONNECT_AUDIO),
            MeetingSessionStatusCode.audioDisconnectAudio
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_ERR_CALL_ENDED),
            MeetingSessionStatusCode.audioCallEnded
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(status: AUDIO_CLIENT_STATUS_ENUM_END),
            MeetingSessionStatusCode.unknown
        )
    }

    func testAudioClientStatusRawValueToMeetingSessionStatusCode() {
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(rawValue: AUDIO_CLIENT_OK.rawValue),
            MeetingSessionStatusCode.ok
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(rawValue: AUDIO_CLIENT_ERR_SERVER_HUNGUP.rawValue),
            MeetingSessionStatusCode.audioServerHungup
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_JOINED_FROM_ANOTHER_DEVICE.rawValue
            ),
            MeetingSessionStatusCode.audioJoinedFromAnotherDevice
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_INTERNAL_SERVER_ERROR.rawValue
            ),
            MeetingSessionStatusCode.audioInternalServerError
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_AUTH_REJECTED.rawValue
            ),
            MeetingSessionStatusCode.audioAuthenticationRejected
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_CALL_AT_CAPACITY.rawValue
            ),
            MeetingSessionStatusCode.audioCallAtCapacity
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_SERVICE_UNAVAILABLE.rawValue
            ),
            MeetingSessionStatusCode.audioServiceUnavailable
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_SHOULD_DISCONNECT_AUDIO.rawValue
            ),
            MeetingSessionStatusCode.audioDisconnectAudio
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_ERR_CALL_ENDED.rawValue
            ),
            MeetingSessionStatusCode.audioCallEnded
        )
        XCTAssertEqual(
            Converters.AudioClientStatus.toMeetingSessionStatusCode(
                rawValue: AUDIO_CLIENT_STATUS_ENUM_END.rawValue
            ),
            MeetingSessionStatusCode.unknown
        )
    }

    func testAudioClientStateToSessionStateControllerAction() {
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_UNKNOWN),
            SessionStateControllerAction.unknown
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_INIT),
            SessionStateControllerAction.initialize
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_CONNECTING),
            SessionStateControllerAction.connecting
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_CONNECTED),
            SessionStateControllerAction.finishConnecting
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_RECONNECTING),
            SessionStateControllerAction.reconnecting
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_DISCONNECTING),
            SessionStateControllerAction.disconnecting
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_DISCONNECTED_NORMAL),
            SessionStateControllerAction.finishDisconnecting
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL),
            SessionStateControllerAction.fail
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_SERVER_HUNGUP),
            SessionStateControllerAction.fail
        )
        XCTAssertEqual(
            Converters.AudioClientState.toSessionStateControllerAction(state: AUDIO_CLIENT_STATE_FAILED_TO_CONNECT),
            SessionStateControllerAction.fail
        )
    }

    func testTranscriptToTranscriptionStatusType() {
        XCTAssertEqual(
            Converters.Transcript.toTranscriptionStatusType(type: TranscriptionStatusTypeInternal.started),
            TranscriptionStatusType.started
        )
        XCTAssertEqual(
            Converters.Transcript.toTranscriptionStatusType(type: TranscriptionStatusTypeInternal.interrupted),
            TranscriptionStatusType.interrupted
        )
        XCTAssertEqual(
            Converters.Transcript.toTranscriptionStatusType(type: TranscriptionStatusTypeInternal.resumed),
            TranscriptionStatusType.resumed
        )
        XCTAssertEqual(
            Converters.Transcript.toTranscriptionStatusType(type: TranscriptionStatusTypeInternal.stopped),
            TranscriptionStatusType.stopped
        )
        XCTAssertEqual(
            Converters.Transcript.toTranscriptionStatusType(type: TranscriptionStatusTypeInternal.failed),
            TranscriptionStatusType.failed
        )
    }

    func testTranscriptToTranscriptItemType() {
        XCTAssertEqual(
            Converters.Transcript.toTranscriptItemType(type: TranscriptItemTypeInternal.pronunciation),
            TranscriptItemType.pronunciation
        )
        XCTAssertEqual(
            Converters.Transcript.toTranscriptItemType(type: TranscriptItemTypeInternal.punctuation),
            TranscriptItemType.punctuation
        )
    }

    func testTranscriptToAttendeeInfo() {
        let attendeeId = "attendee-id"
        let externalUserId = "external-user-id"
        let speakerBefore = AttendeeInfoInternal(attendeeId: attendeeId, externalUserId: externalUserId)!
        let speakerAfter = Converters.Transcript.toAttendeeInfo(attendeeInfo: speakerBefore)
        XCTAssertEqual(speakerAfter.attendeeId, attendeeId)
        XCTAssertEqual(speakerAfter.externalUserId, externalUserId)
    }
}


//
//  DateUtilsTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

@testable import AmazonChimeSDK
import XCTest

class DateUtilsTests: XCTestCase {
    func testGetClientUtcOffsetForPositiveUtcOffsetSeconds() {
        XCTAssertEqual(DateUtils.getFormattedUtcOffset(offsetSeconds: 3600), "+01:00")
    }

    func testGetClientUtcOffsetForNegativeUtcOffsetSeconds() {
        XCTAssertEqual(DateUtils.getFormattedUtcOffset(offsetSeconds: -3600), "-01:00")
    }
}


//
//  DictionaryExtensionTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class DictionaryExtensionTests: XCTestCase {
    private var emptyDict: [String: Int] = [:]
    private var dict1 = ["a": 1]
    private var dict2 = ["a": 2]
    private var dict3 = ["a": 1, "b": 2]
    private var dict4 = ["b": 2]

    func testSubtractingShouldReturnUpdatedValueWhenUpdatingValue() {
        XCTAssertEqual(dict1, dict1.subtracting(dict: dict2))
    }

    func testSubtractingShouldReturnNewEntryWhenPassingInNewEntry() {
        XCTAssertEqual(dict4, dict3.subtracting(dict: dict1))
    }

    func testSubtractingShouldReturnEmptyWhenSubtractingItself() {
        XCTAssertEqual(emptyDict, dict1.subtracting(dict: dict1))
    }

    func testSubtractingShouldIgnoreNonexistedKey() {
        XCTAssertEqual(dict2, dict2.subtracting(dict: dict3))
        XCTAssertEqual(emptyDict, dict1.subtracting(dict: dict3))
    }
}


//
//  EventAttributeUtilsTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class EventAttributeUtilsTests: XCTestCase {

    func testGetCommonAttributesShouldReturnBothCommonAndMetadataAttributes() {
        let meetingId = "test_meeting_id_123"
        let attendeeId = "test_attendee_id_123"
        let clientConfig = MeetingEventClientConfiguration(eventClientJoinToken: "",
                                                           meetingId: meetingId,
                                                           attendeeId: attendeeId)
        let ingestionConfig = IngestionConfiguration(clientConfiguration: clientConfig,
                                                     ingestionUrl: "",
                                                     disabled: false,
                                                     flushSize: 1,
                                                     flushIntervalMs: 1,
                                                     retryCountLimit: 1)
        let attributes = EventAttributeUtils.getCommonAttributes(ingestionConfiguration: ingestionConfig)
        for commonAttribute in EventAttributeUtils.commonEventAttributes {
            XCTAssertNotNil(attributes[commonAttribute.key])
        }
        XCTAssertEqual(attributes[EventAttributeName.meetingId.description] as? String, meetingId)
        XCTAssertEqual(attributes[EventAttributeName.attendeeId.description] as? String, attendeeId)
    }

    func testGetCommonAttributesShouldReturnBothCommonAndMeetingInfoAttributes() {
        let meetingId = "test_meeting_id_123"
        let attendeeId = "test_attendee_id_123"
        let externalMeetingId = "test_external_meeting_id_123"
        let externalUserId = "test_external_user_id_123"
        let credentials = MeetingSessionCredentials(attendeeId: attendeeId,
                                                    externalUserId: externalUserId,
                                                    joinToken: "")
        let meetingSessionUrls = MeetingSessionURLs(audioFallbackUrl: "",
                                                    audioHostUrl: "",
                                                    turnControlUrl: "",
                                                    signalingUrl: "",
                                                    urlRewriter: URLRewriterUtils.defaultUrlRewriter)
        let config = MeetingSessionConfiguration(meetingId: meetingId,
                                                 externalMeetingId: externalMeetingId,
                                                 credentials: credentials,
                                                 urls: meetingSessionUrls,
                                                 urlRewriter: URLRewriterUtils.defaultUrlRewriter)

        let attributes = EventAttributeUtils.getCommonAttributes(meetingSessionConfig: config)
        for commonAttribute in EventAttributeUtils.commonEventAttributes {
            XCTAssertNotNil(attributes[commonAttribute.key])
        }
        XCTAssertEqual(attributes[EventAttributeName.meetingId.description] as? String, meetingId)
        XCTAssertEqual(attributes[EventAttributeName.attendeeId.description] as? String, attendeeId)
        XCTAssertEqual(attributes[EventAttributeName.externalMeetingId.description] as? String, externalMeetingId)
        XCTAssertEqual(attributes[EventAttributeName.externalUserId.description] as? String, externalUserId)
    }
}


//
//  HttpUtilsTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

@testable import AmazonChimeSDK
import Mockingbird
import XCTest

let url = "https://example.com"

class HttpUtilsTests: XCTestCase {
    func testPostShouldReturnErrorIfURLSessionReturnError() {
        HttpUtils.post(url: url, jsonData: nil, logger: nil, urlSession: URLSessionMockErrorImp(), completion: { data, error in
            XCTAssertNotNil(error)
        })
    }

    func testPostShouldReturn400IfURLSEssionReturn400() {
        HttpUtils.post(url: url, jsonData: nil, logger: nil, urlSession: URLSessionMock400Imp(), completion: { _, error in
            if let error = error {
                XCTAssertEqual(400, (error as NSError).code)
            } else {
                XCTFail("Error null")
            }
        })
    }

    func testPostShouldReturn200IfURLSessionReturn200() {
        HttpUtils.post(url: url, jsonData: nil, logger: nil, urlSession: URLSessionMock200Imp(), completion: {data, error in
            XCTAssertNil(error)
        })
    }
}

class URLSessionDataTaskMock: URLSessionDataTask {
    private let closure: () -> Void

    init(closure: @escaping () -> Void) {
        self.closure = closure
    }

    // We override the 'resume' method and simply call our closure
    // instead of actually resuming any task.
    override func resume() {
        closure()
    }
}

class URLSessionMock400Imp: URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        completionHandler(nil, HTTPURLResponse(url: URL(string: url)!, statusCode: 400, httpVersion: "2.0", headerFields: nil), nil)
        return URLSessionDataTaskMock {}
    }
}

class URLSessionMock200Imp: URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        completionHandler(nil, HTTPURLResponse(url: URL(string: url)!, statusCode: 200, httpVersion: "2.0", headerFields: nil), nil)
        return URLSessionDataTaskMock {}
    }
}

class URLSessionMockErrorImp: URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        completionHandler(nil, HTTPURLResponse(url: URL(string: url)!, statusCode: 500, httpVersion: "2.0", headerFields: nil), nil)
        return URLSessionDataTaskMock {}
    }
}


//
//  SQLiteClientFileTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Foundation
import SQLite3
import XCTest

class SQLiteClientFileTests: SQLiteClientTests {
    override func setUp() {
        let loggerMock = mock(Logger.self)

        sqliteDBClient = SQLiteClient(databaseName: "db_in_file.db", logger: loggerMock)
        createTable(tableName: tableName)
    }

    override func tearDown() {
        sqliteDBClient?.write(statement: "DELETE FROM \(tableName)")
        sqliteDBClient?.close()
    }
}


//
//  SQLiteClientTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Foundation
import SQLite3
import XCTest

class SQLiteClientTests: XCTestCase {
    let tableName = "Test"
    let insertValue = "Hello"
    let insertNumber = Int64(1777)

    var sqliteDBClient: SQLiteClient?

    private let pkName = "id"
    private let dataName = "data"
    private let numName = "num"

    override func setUp() {
        let loggerMock = mock(Logger.self)

        // :memory: is needed for in memory databse
        sqliteDBClient = SQLiteClient(databaseName: ":memory:", logger: loggerMock, inMemory: true)
        createTable(tableName: tableName)
    }

    override func tearDown() {
        dropTable()
        sqliteDBClient?.close()
    }

    func createTable(tableName: String) {
        sqliteDBClient?.write(statement: "CREATE TABLE IF NOT EXISTS \(tableName) (\(pkName) INTEGER PRIMARY KEY AUTOINCREMENT, \(dataName) TEXT, \(numName) INTEGER);")
    }

    func testCreateTableWithMalformedStatementShouldFail() {
        let isCreated = sqliteDBClient?.write(statement: "CREATE TABLE IF NOT EXISTS \(tableName) (id ZZZZZ;ei PRIMARY KEY CHICKEN, \(dataName) NO KFC TODAY);")

        XCTAssertFalse(isCreated ?? true)
    }

    func testCreateTableShouldNotFailWhenCreatingExistingTable() {
        let isCreated = sqliteDBClient?.write(statement: "CREATE TABLE IF NOT EXISTS \(tableName) (\(pkName) INTEGER PRIMARY KEY AUTOINCREMENT, \(dataName) TEXT, \(numName) INTEGER);")

        XCTAssertTrue(isCreated ?? false)
    }

    func testWriteShouldInsertASingleItem() {
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(),
                                             params: [insertValue, insertNumber])

        XCTAssertEqual(inserted, true)

        let result = sqliteDBClient?.query(statement: getQueryStatement())
        XCTAssertEqual(insertValue, result?[0][dataName] as? String)
        XCTAssertEqual(insertNumber, result?[0][numName] as? Int64)
    }

    func testWriteShouldInsertMultipleItems() {
        let inserted1 = sqliteDBClient?.write(statement: getInsertStatement(), params: ["Hello1", 1])
        let inserted2 = sqliteDBClient?.write(statement: getInsertStatement(), params: ["Hello2", 2])

        XCTAssertEqual(inserted1, true)
        XCTAssertEqual(inserted2, true)

        let result = sqliteDBClient?.query(statement: getQueryStatement())

        XCTAssertEqual(result?[0][dataName] as? String, "Hello1")
        XCTAssertEqual(result?[1][dataName] as? String, "Hello2")
    }

    func testWriteShouldInsertMultipleItemsWithMultipleValues() {
        let inserted = sqliteDBClient?.write(statement: "INSERT INTO \(self.tableName) (\(dataName), \(numName)) VALUES (?, ?),(?, ?);", params: ["Hello1", 1, "Hello2", 2])

        XCTAssertEqual(inserted, true)

        let result = sqliteDBClient?.query(statement: getQueryStatement())

        XCTAssertEqual(result?[0][dataName] as? String, "Hello1")
        XCTAssertEqual(result?[1][dataName] as? String, "Hello2")
    }

    func testWriteShouldFailWhenFewerParamsAreGiven() {
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(), params: [])

        XCTAssertEqual(inserted, false)
    }

    func testWriteShouldFailWhenMoreParamsAreGiven() {
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(), params: ["a", 5, "c"])

        XCTAssertEqual(inserted, false)
    }

    func testWriteShouldInsertMultipleItemsConcurrently() {
        let backgroundThreadEndedExpectation1 = XCTestExpectation(
            description: "The background thread 1 was ended")
        let backgroundThreadEndedExpectation2 = XCTestExpectation(
            description: "The background thread 2 was ended")

        DispatchQueue.global(qos: .userInteractive).async {
            sleep(2)
            let inserted = self.sqliteDBClient?.write(statement: self.getInsertStatement(), params: ["Hello1", 5])
            XCTAssertEqual(inserted, true)
            backgroundThreadEndedExpectation1.fulfill()
        }
        DispatchQueue.global(qos: .userInteractive).async {
            sleep(2)
            let inserted = self.sqliteDBClient?.write(statement: self.getInsertStatement(), params: ["Hello2", 2])
            XCTAssertEqual(inserted, true)
            backgroundThreadEndedExpectation2.fulfill()
        }

        wait(for: [backgroundThreadEndedExpectation1, backgroundThreadEndedExpectation2], timeout: 3)

        let result = sqliteDBClient?.query(statement: getQueryStatement(), params: nil)

        XCTAssertEqual(result?.count, 2)
    }

    func testQueryShouldBindStringAndInteger() {
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(),
                                             params: [insertValue, insertNumber])

        XCTAssertEqual(inserted, true)

        let result1 = sqliteDBClient?.query(statement: "\(getQueryStatement()) LIMIT 1;", params: nil)
        let result2 = sqliteDBClient?.query(statement: "\(getQueryStatement()) WHERE id=?;", params: [result1?[0][pkName]])

        XCTAssertEqual(result1?[0][pkName] as? Int, result2?[0][pkName] as? Int)
        XCTAssertNotNil(result1?[0][dataName] as? String)
        XCTAssertEqual(result1?[0][dataName] as? String, result2?[0][dataName] as? String)

    }

    func testQueryShouldReturnEmptyWhenNoEntryFound() {
        let result = sqliteDBClient?.query(statement: "\(getQueryStatement()) WHERE id=?;", params: ["randomId"])

        XCTAssertEqual(0, result?.count)
    }

    func testDeleteShouldReturnTrueWhenNoEntryFound() {
        let result = sqliteDBClient?.write(statement: "\(getDeleteStatement()) WHERE id=?;", params: ["randomId"])

        XCTAssertTrue(result ?? false)
    }

    func testDeleteShouldReturnTrueWhenDeleteAnItem() {
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(),
                                             params: [insertValue, insertNumber])

        XCTAssertTrue(inserted ?? false)

        let deleted = sqliteDBClient?.write(statement: "\(getDeleteStatement()) WHERE \(numName)=?;", params: [insertNumber])

        XCTAssertTrue(deleted ?? false)
    }

    func testInsertShouldNotThrowErrorWhenFailed() {
        dropTable()
        let inserted = sqliteDBClient?.write(statement: getInsertStatement(),
                                             params: [insertValue, insertNumber])

        XCTAssertEqual(inserted, false)
    }

    func testQueryShouldReturnEmptyListWhenFailed() {
        dropTable()
        let result1 = sqliteDBClient?.query(statement: "\(getQueryStatement()) LIMIT 10;", params: nil)

        XCTAssertEqual(result1?.count, 0)
    }

    func testDeleteShouldNotThrowErrorWhenFailed() {
        dropTable()
        let deleted = sqliteDBClient?.write(statement: getDeleteStatement())
        XCTAssertEqual(deleted, false)
    }

    private func getInsertStatement() -> String {
        return "INSERT INTO \(self.tableName) (\(dataName), \(numName)) VALUES (?, ?);"
    }

    private func getQueryStatement() -> String {
        return "SELECT \(pkName), \(dataName), \(numName) FROM \(tableName)"
    }

    private func getDeleteStatement() -> String {
        return "DELETE FROM \(tableName)"
    }

    private func dropTable() {
        sqliteDBClient?.write(statement: "DROP TABLE \(tableName);")
    }
}


//
//  DefaultVideoClientControllerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import Mockingbird
import XCTest

class DefaultVideoClientControllerTests: CommonTestCase {
    let topic = "topic"
    let testMessage = "test"

    var videoClientMock: VideoClientProtocolMock!
    var clientMetricsCollectorMock: ClientMetricsCollectorMock!
    var eventAnalyticsControllerMock: EventAnalyticsControllerMock!
    var videoSourceMock: VideoSourceMock!

    var defaultVideoClientController: DefaultVideoClientController!

    override func setUp() {
        super.setUp()

        videoSourceMock = mock(VideoSource.self)
        videoClientMock = mock(VideoClientProtocol.self)
        clientMetricsCollectorMock = mock(ClientMetricsCollector.self)
        eventAnalyticsControllerMock = mock(EventAnalyticsController.self)

        defaultVideoClientController = DefaultVideoClientController(videoClient: videoClientMock,
                                                                    clientMetricsCollector: clientMetricsCollectorMock,
                                                                    configuration: meetingSessionConfigurationMock,
                                                                    logger: loggerMock,
                                                                    eventAnalyticsController: eventAnalyticsControllerMock)
        given(videoSourceMock.getVideoContentHint()).willReturn(VideoContentHint.text)
    }

    func testSendDataMessage_videoClientNotStarted() {
        XCTAssertNoThrow(try defaultVideoClientController.sendDataMessage(topic: topic, data: testMessage))

        verify(loggerMock.error(msg: "Cannot send data message because videoClientState=uninitialized")).wasCalled()
        verify(videoClientMock.sendDataMessage(any(), data: any(), dataLen: any(), lifetimeMs: any())).wasNeverCalled()
    }

    func testSendDataMessage_negativeLifetimeMs() {
        defaultVideoClientController.start()
        XCTAssertThrowsError(try defaultVideoClientController.sendDataMessage(topic: topic, data: testMessage, lifetimeMs: -1)) { error in
            XCTAssertEqual(error as? SendDataMessageError, SendDataMessageError.negativeLifetimeParameter)
        }

        verify(videoClientMock.sendDataMessage(any(), data: any(), dataLen: any(), lifetimeMs: any())).wasNeverCalled()
    }

    func testSendDataMessage_invalidTopic() {
        defaultVideoClientController.start()
        XCTAssertThrowsError(try defaultVideoClientController.sendDataMessage(topic: "$invalid$", data: testMessage)) { error in
            XCTAssertEqual(error as? SendDataMessageError, SendDataMessageError.invalidTopic)
        }

        verify(videoClientMock.sendDataMessage(any(), data: any(), dataLen: any(), lifetimeMs: any())).wasNeverCalled()
    }

    func testSendDataMessage_sendString() {
        defaultVideoClientController.start()
        XCTAssertNoThrow(try defaultVideoClientController.sendDataMessage(topic: topic, data: testMessage))

        verify(videoClientMock.sendDataMessage(self.topic, data: any(), dataLen: any(), lifetimeMs: 0)).wasCalled()
    }

    func testSendDataMessage_sendByteArray() {
        defaultVideoClientController.start()
        XCTAssertNoThrow(try defaultVideoClientController.sendDataMessage(topic: topic, data: [116, 101, 115, 116]))

        verify(videoClientMock.sendDataMessage(self.topic, data: any(), dataLen: any(), lifetimeMs: 0)).wasCalled()
    }

    func testSendDataMessage_sendJson() {
        defaultVideoClientController.start()
        XCTAssertNoThrow(try defaultVideoClientController.sendDataMessage(topic: topic, data: ["key": "value"]))

        verify(videoClientMock.sendDataMessage(self.topic, data: any(), dataLen: any(), lifetimeMs: 0)).wasCalled()
    }

    func testSendDataMessage_sendInvalidData() {
        defaultVideoClientController.start()
        XCTAssertThrowsError(try defaultVideoClientController.sendDataMessage(topic: topic, data: 0)) { error in
            XCTAssertEqual(error as? SendDataMessageError, SendDataMessageError.invalidData)
        }

        verify(videoClientMock.sendDataMessage(self.topic, data: any(), dataLen: any(), lifetimeMs: 0)).wasNeverCalled()
    }

    func testSendLocalVideo() {
        defaultVideoClientController.start()
        XCTAssertNoThrow(try defaultVideoClientController.startLocalVideo())

        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
    }

    func testSendLocalVideoWithConfig() {
        defaultVideoClientController.start()
        let config = LocalVideoConfiguration(maxBitRateKbps: 300)
        XCTAssertNoThrow(try defaultVideoClientController.startLocalVideo(config: config))

        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
        verify(videoClientMock.setSimulcast(true)).wasCalled()
        verify(videoClientMock.setMaxBitRateKbps(300)).wasCalled()
    }

    func testSendLocalVideoWithSource() {
        defaultVideoClientController.start()
        defaultVideoClientController.startLocalVideo(source: videoSourceMock)

        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
    }

    func testSendLocalVideoWithSourceAndConfig() {
        defaultVideoClientController.start()
        let config = LocalVideoConfiguration(maxBitRateKbps: 300)
        defaultVideoClientController.startLocalVideo(source: videoSourceMock, config: config)

        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
        verify(videoClientMock.setSimulcast(true)).wasCalled()
        verify(videoClientMock.setMaxBitRateKbps(300)).wasCalled()
    }
}


//
//  VideoClientStateTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VideoClientStateTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(VideoClientState.uninitialized.description, "uninitialized")
        XCTAssertEqual(VideoClientState.initialized.description, "initialized")
        XCTAssertEqual(VideoClientState.started.description, "started")
        XCTAssertEqual(VideoClientState.stopped.description, "stopped")
    }
}


//
//  DefaultMeetingSessionTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class DefaultMeetingSessionTests: XCTestCase {
    private let audioFallbackStr = "audioFallbackUrl"
    private let audioHostStr = "audioHostUrl"
    private let attendeeIdStr = "attendeeId"
    private let externalMeetingIdStr = "externalMeetingId"
    private let externalUserIdStr = "externalUserId"
    private let joinTokenStr = "joinToken"
    private let mediaRegionStr = "mediaRegion"
    private let meetingIdStr = "meetingId"
    private let signalingUrlStr = "signalingUrl"
    private let turnControlUrlStr = "turnControlUrl"

    private var meetingSession: DefaultMeetingSession?

    override func setUp() {
        super.setUp()
        let mediaPlacement = MediaPlacement(audioFallbackUrl: audioFallbackStr,
                                            audioHostUrl: audioHostStr,
                                            signalingUrl: signalingUrlStr,
                                            turnControlUrl: turnControlUrlStr)
        let meeting = Meeting(externalMeetingId: externalMeetingIdStr,
                              mediaPlacement: mediaPlacement,
                              mediaRegion: mediaRegionStr,
                              meetingId: meetingIdStr)
        let attendee = Attendee(attendeeId: attendeeIdStr, externalUserId: externalUserIdStr, joinToken: joinTokenStr)
        let configuration = MeetingSessionConfiguration(
            createMeetingResponse: CreateMeetingResponse(meeting: meeting),
            createAttendeeResponse: CreateAttendeeResponse(attendee: attendee)
        )
        let logger = ConsoleLogger(name: "test")
        meetingSession = DefaultMeetingSession(configuration: configuration, logger: logger)
    }

    func testMeetingSessionShouldBeInitialized() throws {
        XCTAssertNotNil(meetingSession)
        XCTAssertNotNil(meetingSession?.audioVideo)
    }
}


//
//  MeetingSessionConfigurationTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class MeetingSessionConfigurationTests: XCTestCase {
    private let audioFallbackStr = "audioFallbackUrl"
    private let audioHostStr = "audioHostUrl"
    private let attendeeIdStr = "attendeeId"
    private let externalMeetingIdStr = "externalMeetingId"
    private let externalUserIdStr = "externalUserId"
    private let joinTokenStr = "joinToken"
    private let mediaRegionStr = "mediaRegion"
    private let meetingIdStr = "meetingId"
    private let signalingUrlStr = "signalingUrl"
    private let turnControlUrlStr = "turnControlUrl"
    private let ingestionUrlStr = "ingestionUrl"

    private var mediaPlacement: MediaPlacement?
    private var meeting: Meeting?
    private var meetingResponse: CreateMeetingResponse?
    private var attendee: Attendee?
    private var attendeeResponse: CreateAttendeeResponse?
    private var configuration: MeetingSessionConfiguration?

    override func setUp() {
        super.setUp()
        mediaPlacement = MediaPlacement(audioFallbackUrl: audioFallbackStr,
                                        audioHostUrl: audioHostStr,
                                        signalingUrl: signalingUrlStr,
                                        turnControlUrl: turnControlUrlStr,
                                        eventIngestionUrl: ingestionUrlStr)
        if let mediaPlacement = mediaPlacement {
            meeting = Meeting(externalMeetingId: externalMeetingIdStr,
                              mediaPlacement: mediaPlacement,
                              mediaRegion: mediaRegionStr,
                              meetingId: meetingIdStr)
        }

        if let meeting = meeting {
            meetingResponse = CreateMeetingResponse(meeting: meeting)
        }

        attendee = Attendee(attendeeId: attendeeIdStr, externalUserId: externalUserIdStr, joinToken: joinTokenStr)

        if let attendee = attendee {
            attendeeResponse = CreateAttendeeResponse(attendee: attendee)
        }

        if let meetingResponse = meetingResponse, let attendeeResponse = attendeeResponse {
            configuration = MeetingSessionConfiguration(createMeetingResponse: meetingResponse,
                                                        createAttendeeResponse: attendeeResponse)
        }
    }

    func testExternalMeetingIdNilfNotProvidedThroughMeeting() {
        if let mediaPlacement = mediaPlacement, let attendeeResponse = attendeeResponse {
            let localMeeting = Meeting(externalMeetingId: nil,
                              mediaPlacement: mediaPlacement,
                              mediaRegion: mediaRegionStr,
                              meetingId: meetingIdStr)
            let localMeetingResponse = CreateMeetingResponse(meeting: localMeeting)
            let localConfiguration = MeetingSessionConfiguration(createMeetingResponse: localMeetingResponse,
                                                        createAttendeeResponse: attendeeResponse)
            XCTAssertNil(localConfiguration.externalMeetingId)
            return
        }

        XCTFail("Unable retrieve non null mediaPlacement or attendeeResponse")
    }

    let defaultUrlRewriter: URLRewriter = { url in
        url
    }

    func testExternalMeetingIdNilfNotProvidedThroughConstructor() {
        let credentials = MeetingSessionCredentials(attendeeId: attendeeIdStr,
                                                     externalUserId: externalUserIdStr,
                                                     joinToken: joinTokenStr)
        let urls = MeetingSessionURLs(audioFallbackUrl: audioFallbackStr,
                                       audioHostUrl: audioHostStr,
                                       turnControlUrl: turnControlUrlStr,
                                       signalingUrl: signalingUrlStr,
                                       urlRewriter: defaultUrlRewriter)

        let localConfiguration = MeetingSessionConfiguration(meetingId: meetingIdStr,
                                                             credentials: credentials,
                                                             urls: urls,
                                                             urlRewriter: defaultUrlRewriter)

        XCTAssertNil(localConfiguration.externalMeetingId)
    }

    func testIngestionUrlNilfNotProvidedThroughConstructor() {
        let credentials = MeetingSessionCredentials(attendeeId: attendeeIdStr,
                                                     externalUserId: externalUserIdStr,
                                                     joinToken: joinTokenStr)
        let urls = MeetingSessionURLs(audioFallbackUrl: audioFallbackStr,
                                       audioHostUrl: audioHostStr,
                                       turnControlUrl: turnControlUrlStr,
                                       signalingUrl: signalingUrlStr,
                                       urlRewriter: defaultUrlRewriter)

        let localConfiguration = MeetingSessionConfiguration(meetingId: meetingIdStr,
                                                             credentials: credentials,
                                                             urls: urls,
                                                             urlRewriter: defaultUrlRewriter)

        XCTAssertNil(localConfiguration.urls.ingestionUrl)
    }

    func testMediaPlacementShouldBeInitialized() {
        XCTAssertEqual(audioFallbackStr, mediaPlacement?.audioFallbackUrl)
        XCTAssertEqual(audioHostStr, mediaPlacement?.audioHostUrl)
        XCTAssertEqual(signalingUrlStr, mediaPlacement?.signalingUrl)
        XCTAssertEqual(turnControlUrlStr, mediaPlacement?.turnControlUrl)
    }

    func testMeetingShouldBeInitialized() {
        XCTAssertEqual(externalMeetingIdStr, meeting?.externalMeetingId)
        XCTAssertEqual(mediaPlacement?.audioFallbackUrl, meeting?.mediaPlacement.audioFallbackUrl)
        XCTAssertEqual(mediaPlacement?.audioHostUrl, meeting?.mediaPlacement.audioHostUrl)
        XCTAssertEqual(mediaPlacement?.signalingUrl, meeting?.mediaPlacement.signalingUrl)
        XCTAssertEqual(mediaPlacement?.turnControlUrl, meeting?.mediaPlacement.turnControlUrl)
        XCTAssertEqual(mediaRegionStr, meeting?.mediaRegion)
        XCTAssertEqual(meetingIdStr, meeting?.meetingId)
    }

    func testCreateMeetingResponseShouldBeInitialized() {
        XCTAssertEqual(meetingResponse?.meeting.externalMeetingId, meeting?.externalMeetingId)
        XCTAssertEqual(meetingResponse?.meeting.mediaPlacement.audioFallbackUrl,
                       meeting?.mediaPlacement.audioFallbackUrl)
        XCTAssertEqual(meetingResponse?.meeting.mediaPlacement.audioHostUrl, meeting?.mediaPlacement.audioHostUrl)
        XCTAssertEqual(meetingResponse?.meeting.mediaPlacement.signalingUrl, meeting?.mediaPlacement.signalingUrl)
        XCTAssertEqual(meetingResponse?.meeting.mediaPlacement.turnControlUrl, meeting?.mediaPlacement.turnControlUrl)
        XCTAssertEqual(meetingResponse?.meeting.mediaRegion, meeting?.mediaRegion)
        XCTAssertEqual(meetingResponse?.meeting.meetingId, meeting?.meetingId)
    }

    func testAttendeeShouldBeInitialized() {
        XCTAssertEqual(attendeeIdStr, attendee?.attendeeId)
        XCTAssertEqual(externalUserIdStr, attendee?.externalUserId)
        XCTAssertEqual(joinTokenStr, attendee?.joinToken)
    }

    func testCreateAttendeeResponseShouldBeInitialized() {
        XCTAssertEqual(attendee?.attendeeId, attendeeResponse?.attendee.attendeeId)
        XCTAssertEqual(attendee?.externalUserId, attendeeResponse?.attendee.externalUserId)
        XCTAssertEqual(attendee?.joinToken, attendeeResponse?.attendee.joinToken)
    }

    func testMeetingSessionConfigurationShouldBeInitialized() {
        XCTAssertEqual(mediaPlacement?.audioFallbackUrl, configuration?.urls.audioFallbackUrl)
        XCTAssertEqual(mediaPlacement?.audioHostUrl, configuration?.urls.audioHostUrl)
        XCTAssertEqual(mediaPlacement?.signalingUrl, configuration?.urls.signalingUrl)
        XCTAssertEqual(mediaPlacement?.turnControlUrl, configuration?.urls.turnControlUrl)
        XCTAssertEqual(meeting?.meetingId, configuration?.meetingId)
        XCTAssertEqual(attendee?.attendeeId, configuration?.credentials.attendeeId)
        XCTAssertEqual(attendee?.joinToken, configuration?.credentials.joinToken)
    }
}


//
//  MeetingSessionCredentialsTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class MeetingSessionCredentialsTests: XCTestCase {
    func testMeetingSessionCredentialsShouldBeInitialized() {
        let credentials = MeetingSessionCredentials(
            attendeeId: "attendeeId",
            externalUserId: "externalUserId",
            joinToken: "joinToken"
        )

        XCTAssertEqual(credentials.attendeeId, "attendeeId")
        XCTAssertEqual(credentials.externalUserId, "externalUserId")
        XCTAssertEqual(credentials.joinToken, "joinToken")
    }
}


//
//  MeetingSessionStatusCodeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import XCTest

class MeetingSessionStatusCodeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(MeetingSessionStatusCode.ok.description, "ok")
        XCTAssertEqual(MeetingSessionStatusCode.audioDisconnected.description, "audioDisconnected")
        XCTAssertEqual(MeetingSessionStatusCode.connectionHealthReconnect.description, "connectionHealthReconnect")
        XCTAssertEqual(MeetingSessionStatusCode.networkBecomePoor.description, "networkBecomePoor")
        XCTAssertEqual(MeetingSessionStatusCode.audioServerHungup.description, "audioServerHungup")
        XCTAssertEqual(MeetingSessionStatusCode.audioJoinedFromAnotherDevice.description,
                       "audioJoinedFromAnotherDevice")
        XCTAssertEqual(MeetingSessionStatusCode.audioInternalServerError.description, "audioInternalServerError")
        XCTAssertEqual(MeetingSessionStatusCode.audioAuthenticationRejected.description, "audioAuthenticationRejected")
        XCTAssertEqual(MeetingSessionStatusCode.audioCallAtCapacity.description, "audioCallAtCapacity")
        XCTAssertEqual(MeetingSessionStatusCode.audioServiceUnavailable.description, "audioServiceUnavailable")
        XCTAssertEqual(MeetingSessionStatusCode.audioDisconnectAudio.description, "audioDisconnectAudio")
        XCTAssertEqual(MeetingSessionStatusCode.audioCallEnded.description, "audioCallEnded")
        XCTAssertEqual(MeetingSessionStatusCode.videoServiceUnavailable.description, "videoServiceUnavailable")
        XCTAssertEqual(MeetingSessionStatusCode.unknown.description, "unknown")
        XCTAssertEqual(MeetingSessionStatusCode.videoAtCapacityViewOnly.description, "videoAtCapacityViewOnly")
    }
}


//
//  MeetingSessionStatusTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class MeetingSessionStatusTests: XCTestCase {
    func testMeetingSessionStatusShouldBeInitialized() {
        XCTAssertEqual(MeetingSessionStatus(statusCode: MeetingSessionStatusCode.ok).statusCode,
                       MeetingSessionStatusCode.ok)
    }
}


//
//  MeetingSessionURLsTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class MeetingSessionURLsTests: XCTestCase {
    private func rewriteURL(url: String) -> String {
        return url.replacingOccurrences(of: "Url", with: "hello")
    }

    func testMeetingSessionURLsShouldBeInitialized() {
        let url = MeetingSessionURLs(audioFallbackUrl: "audioFallbackUrl",
                                     audioHostUrl: "audioHostUrl",
                                     turnControlUrl: "turnControlUrl",
                                     signalingUrl: "signalingUrl",
                                     urlRewriter: URLRewriterUtils.defaultUrlRewriter)

        XCTAssertEqual(url.audioFallbackUrl, "audioFallbackUrl")
        XCTAssertEqual(url.audioHostUrl, "audioHostUrl")
    }

    func testMeetingSessionURLsShouldBeRewritten() {
        let url = MeetingSessionURLs(audioFallbackUrl: "audioFallbackUrl",
                                     audioHostUrl: "audioHostUrl",
                                     turnControlUrl: "turnControlUrl",
                                     signalingUrl: "signalingUrl",
                                     urlRewriter: rewriteURL)

        XCTAssertEqual(url.audioFallbackUrl, "audioFallbackhello")
        XCTAssertEqual(url.audioHostUrl, "audioHosthello")
        XCTAssertEqual(url.turnControlUrl, "turnControlhello")
        XCTAssertEqual(url.signalingUrl, "signalinghello")
    }
}


//
//  MediaErrorTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class MediaErrorTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(MediaError.illegalState.description, "illegalState")
        XCTAssertEqual(MediaError.audioFailedToStart.description, "audioFailedToStart")
    }
}


//
//  PermissionErrorTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class PermissionErrorTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(PermissionError.audioPermissionError.description, "audioPermissionError")
        XCTAssertEqual(PermissionError.videoPermissionError.description, "videoPermissionError")
    }
}


//
//  SendDataMessageErrorTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Foundation
import XCTest

class SendDataMessageErrorTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(SendDataMessageError.invalidDataLength.description, "invalidDataLength")
        XCTAssertEqual(SendDataMessageError.invalidTopic.description, "invalidTopic")
        XCTAssertEqual(SendDataMessageError.negativeLifetimeParameter.description, "negativeLifetimeParameter")
        XCTAssertEqual(SendDataMessageError.invalidData.description, "invalidData")
    }
}


//
//  VersioningTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VersioningTests: XCTestCase {
    func testsdkVersionShouldReturnCurrentVersion() {
        let sdkVersion = Versioning.sdkVersion()
        let currentVersion = Bundle(for: VersioningTests.self).infoDictionary?["CFBundleShortVersionString"] as? String
            ?? ""
        XCTAssertEqual(sdkVersion, currentVersion)
    }
}


//
//  ConsoleLoggerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class ConsoleLoggerTests: XCTestCase {
    private var logger: ConsoleLogger?

    override func setUp() {
        logger = ConsoleLogger(name: "logger")
    }

    func testConsoleLoggerShouldBeInitialized() {
        XCTAssertEqual(LogLevel.INFO, logger?.getLogLevel())
    }

    func testConsoleLoggerShouldBeSetLogLevelWhenBeingInitializedWithGivenLevel() {
        logger = ConsoleLogger(name: "logger", level: LogLevel.INFO)
        XCTAssertEqual(LogLevel.INFO, logger?.getLogLevel())
    }

    func testsetLogLevelShouldSetLogLevelWhenGivenLogLevel() {
        logger?.setLogLevel(level: LogLevel.FAULT)
        XCTAssertEqual(LogLevel.FAULT, logger?.getLogLevel())
    }
}


//
//  LogLevelTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class LogLevelTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(LogLevel.DEFAULT.description, "DEFAULT")
        XCTAssertEqual(LogLevel.DEBUG.description, "DEBUG")
        XCTAssertEqual(LogLevel.INFO.description, "INFO")
        XCTAssertEqual(LogLevel.FAULT.description, "FAULT")
        XCTAssertEqual(LogLevel.ERROR.description, "ERROR")
        XCTAssertEqual(LogLevel.OFF.description, "OFF")
    }
}


//
//  AppConfiguration.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

struct AppConfiguration {
    static let url = "YOUR_SERVER_URL"
    static let region = "YOUR_SERVER_REGION"
    static let broadcastBundleId = "YOUR_BROADCAST_BUNDLE_ID"
    static let appGroupId = "YOUR_APP_GROUP_ID"
}


//
//  AppDelegate.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_: UIApplication,
                     didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
        window?.makeKeyAndVisible()

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
        }
        return true
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // If app is being force killed while CallKit integrated meeting is in progress,
        // provider(_: CXProvider, perform action: CXEndCallAction) will not be called.
        // So we need to invoke isEndedHandler() directly.
        if let call = MeetingModule.shared().activeMeeting?.call {
            call.isEndedHandler?()
        } else {
            MeetingModule.shared().endActiveMeeting {}
        }
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        if let meeting = MeetingModule.shared().activeMeeting {
            meeting.isAppInBackground = true
        }
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        if let meeting = MeetingModule.shared().activeMeeting {
            meeting.isAppInBackground = false
        }
    }
}


//
//  AutomaticLanguageOptionViewController.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation
import AmazonChimeSDK
import UIKit

class AutomaticLanguageOptionsViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var languageOptionsTableView: UITableView!
    @IBOutlet var preferredLanguageTextField: UITextField!
    var preferredLanguagePickerView = UIPickerView()
    public var cancellationHandler: ((Int?) -> Void)?
    public var selectedLanguageOptionsHandler: ((String?) -> Void)?
    public var preferredLanguageOptionHandler: ((String?) -> Void)?
    public var languages: [String] = []
    public var languagesDict: [String: String] = [:]

    var preferredLanguageDropDown = ["-- Optional --"]
    var selectedLanguageOptions = ""
    var preferredLanguageSelected = ""
    var duplicatedLocaleSelected = ""
    var languageGroups: Set<String> = ["English", "Spanish", "French", "Italian", "German", "Portuguese", "Japanese", "Korean", "Chinese"]
    var duplicatedLocales: [String: Int] = [:]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.preferredLanguagePickerView.delegate = self
        self.preferredLanguagePickerView.dataSource = self
        self.preferredLanguageTextField.delegate = self
        self.preferredLanguageTextField.inputView = preferredLanguagePickerView
        preferredLanguageTextField.text = preferredLanguageDropDown[0]
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return false
    }

    @IBAction func saveLanguageOptions(_ sender: Any) {
        if !validateLanguageOptions() {
            self.view.makeToast("Multiple language variant selected for locale: \(duplicatedLocaleSelected).\nPlease select one language locale per variant.")
            return
        }
        if preferredLanguageDropDown.count < 3 {
            self.view.makeToast("Select a minimum of 2 Language Options")
            return
        } else {

            let selectedIndexPaths = getSelectedIndexPaths()
            selectedLanguageOptions = ""
            for index in selectedIndexPaths.indices.dropLast() {
                let indexPath = selectedIndexPaths[index]
                selectedLanguageOptions += languages[indexPath.row] + ","
            }
            selectedLanguageOptions += languages[selectedIndexPaths.last!.row]
            selectedLanguageOptionsHandler?(selectedLanguageOptions)
            preferredLanguageOptionHandler?(preferredLanguageSelected)
            self.dismiss(animated: true)
        }
    }

    @IBAction func cancelLanguageOptions(_ sender: Any) {
        let selectedIndexPaths = getSelectedIndexPaths()
        for indexPath in selectedIndexPaths {
            preferredLanguageDropDown.removeAll(where: { languagesDict[languages[indexPath.row]]?.elementsEqual($0) ?? false})
            languageOptionsTableView.deselectRow(at: indexPath, animated: true)
        }
        cancellationHandler?(preferredLanguageDropDown.count)
        self.dismiss(animated: true)
    }

    private func getSelectedIndexPaths() -> [IndexPath] {
        var selectedIndexPaths: [IndexPath] = []
        if let indexes = languageOptionsTableView.indexPathsForSelectedRows {
            for iPath in indexes {
                selectedIndexPaths.append(iPath)
            }
        }
        return selectedIndexPaths
    }

    private func validateLanguageOptions() -> Bool {
        for languageGroup in duplicatedLocales.keys {
            if duplicatedLocales[languageGroup]! > 1 {
                duplicatedLocaleSelected = languageGroup
                return false
            }
        }
        return true
    }
}

extension AutomaticLanguageOptionsViewController: UITableViewDelegate, UITableViewDataSource, UIPickerViewDataSource, UIPickerViewDelegate {

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
       return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        preferredLanguageDropDown.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return preferredLanguageDropDown[row]
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        preferredLanguageTextField.text = preferredLanguageDropDown[row]
        preferredLanguageSelected =
            ((languagesDict as NSDictionary).allKeys(for: preferredLanguageDropDown[row]).first as? String) ?? "-- Optional --"
        preferredLanguageTextField.resignFirstResponder()
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return languages.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        languageOptionsTableView.register(UITableViewCell.self, forCellReuseIdentifier: "languageOptionsCell")
        let cell = languageOptionsTableView.dequeueReusableCell(withIdentifier: "languageOptionsCell", for: indexPath)
        cell.textLabel?.text = languagesDict[languages[indexPath.row]]
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedLanguage = languagesDict[languages[indexPath.row]]!
        preferredLanguageDropDown.append(selectedLanguage)
        preferredLanguageSelected = ""
        preferredLanguageTextField.text = preferredLanguageDropDown[0]
        updateDuplicatedLocales(selectedLanguage: selectedLanguage, count: 1)
    }

    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        preferredLanguageDropDown.removeAll(where: { languagesDict[languages[indexPath.row]]?.elementsEqual($0) ?? false})
        preferredLanguageSelected = ""
        preferredLanguageTextField.text = preferredLanguageDropDown[0]
        updateDuplicatedLocales(selectedLanguage: languagesDict[languages[indexPath.row]]!, count: -1)
    }

    private func updateDuplicatedLocales(selectedLanguage: String, count: Int) {
        for languageGroup in languageGroups {
            if selectedLanguage.contains(languageGroup) {
                duplicatedLocales[languageGroup, default: 0] += count
            }
        }
    }

}


//
//  BroadcastScreenCaptureModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import Foundation

class BroadcastScreenCaptureModel {
    let meetingSessionConfig: MeetingSessionConfiguration
    let appGroupUserDefaults = UserDefaults(suiteName: AppConfiguration.appGroupId)
    let userDefaultsKeyMeetingId = "demoMeetingId"
    let userDefaultsKeyExternalMeetingId = "demoExternalMeetingId"
    let userDefaultsKeyCredentials = "demoMeetingCredentials"
    let userDefaultsKeyUrls = "demoMeetingUrls"
    let logger = ConsoleLogger(name: "BroadcastScreenCaptureModel")
    var observer: NSKeyValueObservation?

    var isBlocked = true {
        willSet(newValue) {
            if newValue == isBlocked {
                return
            }
            if newValue {
                deleteMeetingSessionConfigFromUserDefaults()
            } else {
                saveMeetingSessionConfigToUserDefaults()
            }
        }
    }

    init(meetingSessionConfig: MeetingSessionConfiguration) {
        self.meetingSessionConfig = meetingSessionConfig
    }

    // Broadcast extension is retrieving data from shared App Group User Defaults
    // to recreate the MeetingSessionConfig and share device level content.
    // See AmazonChimeSDKDemoBroadcast/SampleHandler for more details.
    private func saveMeetingSessionConfigToUserDefaults() {
        guard let appGroupUserDefaults = appGroupUserDefaults else {
            logger.error(msg: "App Group User Defaults not found")
            return
        }
        appGroupUserDefaults.set(meetingSessionConfig.meetingId, forKey: userDefaultsKeyMeetingId)
        appGroupUserDefaults.set(meetingSessionConfig.externalMeetingId ?? "", forKey: userDefaultsKeyExternalMeetingId)
        let encoder = JSONEncoder()
        if let credentials = try? encoder.encode(meetingSessionConfig.credentials) {
            appGroupUserDefaults.set(credentials, forKey: userDefaultsKeyCredentials)
        }
        if let urls = try? encoder.encode(meetingSessionConfig.urls) {
            appGroupUserDefaults.set(urls, forKey: userDefaultsKeyUrls)
        }
    }

    private func deleteMeetingSessionConfigFromUserDefaults() {
        guard let appGroupUserDefaults = appGroupUserDefaults else {
            logger.error(msg: "App Group User Defaults not found")
            return
        }
        appGroupUserDefaults.removeObject(forKey: userDefaultsKeyMeetingId)
        appGroupUserDefaults.removeObject(forKey: userDefaultsKeyExternalMeetingId)
        appGroupUserDefaults.removeObject(forKey: userDefaultsKeyCredentials)
        appGroupUserDefaults.removeObject(forKey: userDefaultsKeyUrls)
    }
}


//
//  Call.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import UIKit

class Call {
    static let maxIncomingCallAnswerTime = 30.0

    let uuid: UUID
    let handle: String
    let isOutgoing: Bool

    private var endCallBackgroundTaskId: UIBackgroundTaskIdentifier = .invalid

    var isOnHold = false {
        didSet {
            isOnHoldHandler?(isOnHold)
        }
    }

    var isMuted = false {
        didSet {
            isMutedHandler?(isMuted)
        }
    }

    var isUnansweredCallTimerActive = false {
        didSet {
            if isUnansweredCallTimerActive {
                endCallBackgroundTaskId = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
                DispatchQueue.main.asyncAfter(deadline: .now() + Call.maxIncomingCallAnswerTime) { [weak self] in
                    guard let strongSelf = self else { return }
                    if strongSelf.endCallBackgroundTaskId != .invalid {
                        strongSelf.isUnansweredHandler?()
                        strongSelf.isUnansweredCallTimerActive = false
                    }
                }
            } else {
                UIApplication.shared.endBackgroundTask(endCallBackgroundTaskId)
                endCallBackgroundTaskId = .invalid
            }
        }
    }

    var isConnectingHandler: (() -> Void)?
    var isConnectedHandler: (() -> Void)?
    var isReadytoConfigureHandler: (() -> Void)?
    var isAudioSessionActiveHandler: (() -> Void)?
    var isEndedHandler: (() -> Void)?
    var isOnHoldHandler: ((Bool) -> Void)?
    var isMutedHandler: ((Bool) -> Void)?
    var isUnansweredHandler: (() -> Void)?

    init(uuid: UUID, handle: String, isOutgoing: Bool) {
        self.uuid = uuid
        self.handle = handle
        self.isOutgoing = isOutgoing
    }
}


//
//  CallKitManager.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import AVFoundation
import CallKit
import UIKit

class CallKitManager: NSObject {
    private static var sharedInstance: CallKitManager?

    private let logger = ConsoleLogger(name: "CallKitManager")
    private let callController = CXCallController()
    private let provider: CXProvider

    static func shared() -> CallKitManager {
        if sharedInstance == nil {
            sharedInstance = CallKitManager()
        }
        return sharedInstance!
    }

    override init() {
        let configuration = CXProviderConfiguration(localizedName: "Chime SDK Demo")
        configuration.maximumCallGroups = 1
        configuration.maximumCallsPerCallGroup = 1
        configuration.supportsVideo = true
        configuration.supportedHandleTypes = [.generic]
        configuration.iconTemplateImageData = UIImage(named: "callkit-icon")?.pngData()
        provider = CXProvider(configuration: configuration)
        super.init()
        provider.setDelegate(self, queue: nil)
    }

    deinit {
        provider.invalidate()
    }

    // Start an outging call
    func startOutgoingCall(with call: Call) {
        let handle = CXHandle(type: .generic, value: call.handle)
        let startCallAction = CXStartCallAction(call: call.uuid, handle: handle)
        let transaction = CXTransaction(action: startCallAction)

        callController.request(transaction) { error in
            if let error = error {
                self.logger.error(msg: "Error requesting CXStartCallAction transaction: \(error)")
            } else {
                self.logger.info(msg: "Requested CXStartCallAction transaction successfully")
            }
        }
    }

    // This is normally called after receiving a VoIP Push Notification to handle incoming call
    func reportNewIncomingCall(with call: Call) {
        let handle = CXHandle(type: .generic, value: call.handle)
        let update = CXCallUpdate()
        update.remoteHandle = handle
        update.supportsDTMF = false
        update.supportsHolding = true
        update.supportsGrouping = false
        update.supportsUngrouping = false
        update.hasVideo = false

        provider.reportNewIncomingCall(with: call.uuid, update: update, completion: { error in
            if let error = error {
                self.logger.error(msg: "Error reporting new incoming call: \(error.localizedDescription)")
            } else {
                self.logger.info(msg: "Report new incoming call successfully")
            }
        })

        call.isUnansweredHandler = { [weak self] in
            self?.endCallFromLocal(with: call)
            self?.logger.info(msg: "Incoming call not answered within \(Call.maxIncomingCallAnswerTime) sec")
        }
        call.isUnansweredCallTimerActive = true
    }

    // End the call from the app. This is not needed when user end the call from the native CallKit UI
    func endCallFromLocal(with call: Call) {
        let endCallAction = CXEndCallAction(call: call.uuid)
        let transaction = CXTransaction(action: endCallAction)
        callController.request(transaction, completion: { error in
            if let error = error {
                self.logger.error(msg: "Error requesting CXEndCallAction transaction: \(error)")
            } else {
                self.logger.info(msg: "Requested CXEndCallAction transaction successfully")
            }
        })
    }

    // Mute or unmute from the app. This is to sync the CallKit UI with app UI
    func setMuted(for call: Call, isMuted: Bool) {
        let setMutedAction = CXSetMutedCallAction(call: call.uuid, muted: isMuted)
        let transaction = CXTransaction(action: setMutedAction)
        callController.request(transaction, completion: { error in
            if let error = error {
                self.logger.error(msg: "Error requesting CXSetMutedCallAction transaction: \(error)")
            } else {
                self.logger.info(msg: "Requested CXSetMutedCallAction transaction successfully")
            }
        })
    }

    // This is to resume call from the app. When the interrupting call is ended from Remote,
    // provider::perform::CXSetMutedCallAction will not be called automatically
    func setHeld(with call: Call, isOnHold: Bool) {
        let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: isOnHold)
        let transaction = CXTransaction(action: setHeldCallAction)
        callController.request(transaction, completion: { error in
            if let error = error {
                self.logger.error(msg: "Error requesting CXSetHeldCallAction transaction: \(error)")
            } else {
                self.logger.info(msg: "Requested CXSetHeldCallAction \(isOnHold) transaction successfully")
            }
        })
    }

    // Use this to notify CallKit the call is disconnected
    func reportCallEndedFromRemote(with call: Call, reason: CXCallEndedReason) {
        provider.reportCall(with: call.uuid, endedAt: Date(), reason: reason)
    }
}

// MARK: CXProviderDelegate

extension CallKitManager: CXProviderDelegate {
    func providerDidReset(_: CXProvider) {
        MeetingModule.shared().endActiveMeeting(completion: {})
    }

    func providerDidBegin(_: CXProvider) {}

    func provider(_: CXProvider, perform action: CXStartCallAction) {
        if let meeting = MeetingModule.shared().getMeeting(with: action.callUUID), let call = meeting.call {
            call.isConnectingHandler = { [weak self] in
                self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: Date())
            }
            // This is needed for CallKit to calculate outgoing call duration
            call.isConnectedHandler = { [weak self] in
                self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: Date())
            }
            MeetingModule.shared().joinMeeting(meeting) { success in
                if success {
                    call.isReadytoConfigureHandler?()
                    action.fulfill()
                } else {
                    action.fail()
                }
            }
        } else {
            action.fail()
        }
    }

    func provider(_: CXProvider, perform action: CXAnswerCallAction) {
        if let meeting = MeetingModule.shared().getMeeting(with: action.callUUID), let call = meeting.call {
            MeetingModule.shared().joinMeeting(meeting) { success in
                if success {
                    call.isReadytoConfigureHandler?()
                    call.isUnansweredCallTimerActive = false
                    action.fulfill()
                } else {
                    action.fail()
                }
            }
        } else {
            action.fail()
        }
    }

    func provider(_: CXProvider, perform action: CXEndCallAction) {
        if let meeting = MeetingModule.shared().getMeeting(with: action.callUUID), let call = meeting.call {
            call.isEndedHandler?()
            action.fulfill()
        } else {
            action.fail()
        }
    }

    func provider(_: CXProvider, perform action: CXSetHeldCallAction) {
        if let meeting = MeetingModule.shared().getMeeting(with: action.callUUID), let call = meeting.call {
            call.isOnHold = action.isOnHold
            action.fulfill()
        } else {
            action.fail()
        }
    }

    func provider(_: CXProvider, perform action: CXSetMutedCallAction) {
        if let meeting = MeetingModule.shared().getMeeting(with: action.callUUID), let call = meeting.call {
            call.isMuted = action.isMuted
            action.fulfill()
        } else {
            action.fail()
        }
    }

    func provider(_: CXProvider, timedOutPerforming _: CXAction) {}

    func provider(_: CXProvider, didActivate _: AVAudioSession) {
        if let call = MeetingModule.shared().activeMeeting?.call {
            call.isAudioSessionActiveHandler?()
        }
    }

    func provider(_: CXProvider, didDeactivate _: AVAudioSession) {}
}


//
//  CaptionCell.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import UIKit
import AmazonChimeSDK

let captionCellReuseIdentifier = "captionCell"
let lowItemConfidenceThreshold = 0.3
let filteredCaptionFirstIndex: Character = "["

class CaptionCell: UITableViewCell {
    @IBOutlet var speakerNameLabel: UILabel!
    @IBOutlet var captionContentLabel: UILabel!

    init(caption: Caption, indexPath: IndexPath) {
        super.init(style: .default, reuseIdentifier: captionCellReuseIdentifier)
        self.updateCell(caption: caption, indexPath: indexPath)
    }

    func updateCell(caption: Caption, indexPath: IndexPath) {
        self.contentView.backgroundColor = caption.speakerName.isEmpty ? .lightGray : .none
        speakerNameLabel.text = caption.speakerName
        speakerNameLabel.accessibilityIdentifier = caption.speakerName
        captionContentLabel.accessibilityIdentifier = "caption-\(indexPath.row)"
        // Highlight identified/redacted PIIs in green.
        let coloredString = NSMutableAttributedString.init(string: caption.content)
        caption.entities?.forEach { word in
            var firstOccurance = caption.content.startIndex
            while firstOccurance < caption.content.endIndex,
                  let range = (caption.content).range(of: word, range: firstOccurance..<caption.content.endIndex),
                  !range.isEmpty {
                let index = caption.content.distance(from: caption.content.startIndex, to: range.lowerBound)
                coloredString.addAttribute(
                    NSAttributedString.Key.foregroundColor,
                    value: UIColor.green,
                    range: NSRange(location: index, length: word.count))
                firstOccurance = range.upperBound
            }
        }
        // Underline low confidence words in red.
        caption.items?.forEach { item in
            let word = item.content
            let hasLowConfidence = (item.confidence ?? 1.0) < lowItemConfidenceThreshold && item.confidence != 0
            let isCorrectContentType = word.first != filteredCaptionFirstIndex && item.type != TranscriptItemType.punctuation
            let range = (caption.content as NSString).range(of: word)
            if hasLowConfidence && isCorrectContentType && caption.content.contains(word) {
                coloredString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: 1), range: range)
                coloredString.addAttribute(NSAttributedString.Key.underlineColor, value: UIColor.red, range: range)
            }
        }
        captionContentLabel.attributedText = coloredString
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}


//
//  CaptionsModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import Foundation
import UIKit

class CaptionsModel: NSObject {
    let logger = ConsoleLogger(name: "CaptionsModel")

    var isLiveTranscriptionEnabled = false

    var captions = [Caption]()
    var captionIndices = [String: Int]()    // map resultId to index in captions array
    var refreshCaptionsTableHandler: (() -> Void)?

    // triggered on main thread, no synchronization needed
    public func addTranscriptEvent(transcriptEvent: TranscriptEvent) {
        if let status = transcriptEvent as? TranscriptionStatus {
            let formattedEventTime = TimeStampConversion.formatTimestamp(timestamp: status.eventTimeMs)
            let content = "Live transcription \(status.type) at \(formattedEventTime) in \(status.transcriptionRegion) with configuration: \(status.transcriptionConfiguration)"
            let caption = Caption(speakerName: "",
                                  isPartial: false,
                                  content: content)
            captions.append(caption)
            if status.type == .started || status.type == .stopped {
                isLiveTranscriptionEnabled = status.type == .started
            }
        } else if let transcript = transcriptEvent as? Transcript {
            transcript.results.forEach { result in
                guard let alternative = result.alternatives.first else {
                    return
                }

                // for simplicity and demo purposes, assume each result only contains transcripts from
                // the same speaker, which matches our observation with current transcription service behavior.
                // More complicated UI logic can be achieved by iterating through each item
                var speakerName: String = "<UNKNOWN>"
                if let firstItem = alternative.items.first {
                    speakerName = RosterModel.convertAttendeeName(from: firstItem.attendee)
                } else {
                    logger.debug(debugFunction: {
                        return "Empty speaker name due to empty items array for result: \(result.resultId)"
                    })
                }

                let entities = alternative.entities?.flatMap { $0.content.components(separatedBy: " ")} ?? []
                let caption = Caption(speakerName: speakerName,
                                      isPartial: result.isPartial,
                                      content: alternative.transcript,
                                      entities: entities,
                                      items: alternative.items)

                if let captionIndex = captionIndices[result.resultId] {
                    // update existing (partial) caption if exists
                    captions[captionIndex] = caption
                } else {
                    captions.append(caption)
                    captionIndices[result.resultId] = captions.count - 1
                }
            }
        }
        refreshCaptionsTableHandler?()
    }
}

// MARK: UITableViewDelegate, UITableViewDataSource

extension CaptionsModel: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return captions.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let caption = captions[indexPath.item]
        guard let cell = tableView.dequeueReusableCell(withIdentifier: captionCellReuseIdentifier) as? CaptionCell else {
            return CaptionCell(caption: caption, indexPath: indexPath)
        }
        cell.updateCell(caption: caption, indexPath: indexPath)
        return cell
    }
}


//
//  ChatMessageCell.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import UIKit

let chatMessageCellReuseIdentifier: String = "chatMessageCell"

class ChatMessageCell: UITableViewCell {
    @IBOutlet var senderNameLabel: UILabel!
    @IBOutlet var messageLabel: UILabel!
    @IBOutlet var timestampLabel: UILabel!

    init(chatMessage: ChatMessage) {
        super.init(style: .default, reuseIdentifier: chatMessageCellReuseIdentifier)
        self.updateCell(chatMessage: chatMessage)
    }

    func updateCell(chatMessage: ChatMessage) {
        senderNameLabel.text = chatMessage.senderName
        senderNameLabel.accessibilityIdentifier = chatMessage.senderName
        messageLabel.text = chatMessage.message
        messageLabel.accessibilityIdentifier = chatMessage.message
        timestampLabel.text = chatMessage.timestamp
        messageLabel.textAlignment = chatMessage.isSelf ? NSTextAlignment.right : NSTextAlignment.left
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}


//
//  ChatModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import UIKit

extension DataMessage {
    func chatMessage() -> ChatMessage? {
        let senderNameComponents = self.senderExternalUserId.components(separatedBy: "#")
        let senderName = senderNameComponents.count > 1 ? senderNameComponents[1] : self.senderExternalUserId
        let message = self.text()
        let timestamp = TimeStampConversion.formatTimestamp(timestamp: self.timestampMs)
        if let messageNotNull = message {
            let msg = ChatMessage(senderName: senderName, message: messageNotNull, timestamp: timestamp, isSelf: false)
            return msg
        }
        return nil
    }
}

struct ChatMessage {
    var senderName: String
    var message: String
    var timestamp: String
    var isSelf: Bool
}

class ChatModel: NSObject {
    private var chatMessages: [ChatMessage] = []
    private let logger = ConsoleLogger(name: "ChatModel")
    var refreshChatTableHandler: (() -> Void)?
    public func addDataMessage(dataMessage: DataMessage) {
        guard let message = dataMessage.chatMessage() else {
            return
        }

        if !dataMessage.throttled {
            self.addChatMessage(chatMessage: message)
        }
    }

    public func addChatMessage(chatMessage: ChatMessage) {
        self.chatMessages.append(chatMessage)
        refreshChatTableHandler?()
    }
}

// MARK: UITableViewDelegate, UITableViewDataSource

extension ChatModel: UITableViewDelegate, UITableViewDataSource {
    func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
        return chatMessages.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let chatMessage = chatMessages[indexPath.item]

        guard let cell = tableView
            .dequeueReusableCell(withIdentifier: chatMessageCellReuseIdentifier) as? ChatMessageCell
        else {
            return ChatMessageCell(chatMessage: chatMessage)
        }
        cell.updateCell(chatMessage: chatMessage)
        return cell
    }
}


//
//  DebugSettingsModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import UIKit

class DebugSettingsModel: NSObject {
    var endpointUrl: String = ""
    var primaryExternalMeetingId: String = ""
}


//
//  DebugSettingsViewController.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import UIKit

class DebugSettingsViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet var serverEndpointUrlTextField: UITextField!
    @IBOutlet var primaryExternalMeetingIdTextField: UITextField!
    @IBOutlet var saveButton: UIButton!

    var model: DebugSettingsModel?

    override func viewDidLoad() {
        super.viewDidLoad()

        setupHideKeyboardOnTap()
        serverEndpointUrlTextField.delegate = self
        serverEndpointUrlTextField.text = model?.endpointUrl
        primaryExternalMeetingIdTextField.text = model?.primaryExternalMeetingId
    }

    @IBAction func saveButtonClicked(_: UIButton) {
        let endpointUrl = serverEndpointUrlTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        model?.endpointUrl = endpointUrl

        let primaryExternalMeetingId = primaryExternalMeetingIdTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        model?.primaryExternalMeetingId = primaryExternalMeetingId

        self.dismiss(animated: true, completion: nil)
    }

	func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}


//
//  DeviceSelectionModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import Foundation

class DeviceSelectionModel {
    let audioDevices: [MediaDevice]
    let videoDevices: [MediaDevice]
    let cameraCaptureSource: DefaultCameraCaptureSource
    let audioVideoConfig: AudioVideoConfiguration

    lazy var supportedVideoFormat: [[VideoCaptureFormat]] = {
        self.videoDevices.map { videoDevice in
            // Reverse these so the highest resolutions are first
            MediaDevice.listSupportedVideoCaptureFormats(mediaDevice: videoDevice).reversed()
        }
    }()

    var selectedAudioDeviceIndex = 0
    var selectedVideoDeviceIndex: Int = 0 {
        didSet {
            cameraCaptureSource.device = selectedVideoDevice
        }
    }

    var selectedVideoFormatIndex = 0 {
        didSet {
            guard let selectedVideoFormat = selectedVideoFormat else { return }
            cameraCaptureSource.format = selectedVideoFormat
        }
    }

    var selectedAudioDevice: MediaDevice {
        return audioDevices[selectedAudioDeviceIndex]
    }

    var selectedVideoDevice: MediaDevice? {
        if videoDevices.count == 0 {
            return nil
        }
        return videoDevices[selectedVideoDeviceIndex]
    }

    var selectedVideoFormat: VideoCaptureFormat? {
        if videoDevices.count == 0 {
            return nil
        }
        return supportedVideoFormat[selectedVideoDeviceIndex][selectedVideoFormatIndex]
    }

    var shouldMirrorPreview: Bool {
        return selectedVideoDevice?.type == MediaDeviceType.videoFrontCamera
    }

    init(deviceController: DeviceController, cameraCaptureSource: DefaultCameraCaptureSource, audioVideoConfig: AudioVideoConfiguration) {
        audioDevices = deviceController.listAudioDevices()
        // Reverse these so the front camera is the initial choice
        videoDevices = MediaDevice.listVideoDevices().reversed()
        self.cameraCaptureSource = cameraCaptureSource
        self.audioVideoConfig = audioVideoConfig
        cameraCaptureSource.device = selectedVideoDevice
        guard let selectedVideoFormat = selectedVideoFormat else { return }
        cameraCaptureSource.format = selectedVideoFormat
    }
}


//
//  DeviceSelectionViewController.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import UIKit

class DeviceSelectionViewController: UIViewController {
    @IBOutlet var audioDeviceLabel: UILabel!
    @IBOutlet var audioDevicePicker: UIPickerView!
    @IBOutlet var videoDevicePicker: UIPickerView!
    @IBOutlet var videoFormatPicker: UIPickerView!
    @IBOutlet var videoPreviewImageView: DefaultVideoRenderView!
    @IBOutlet var joinButton: UIButton!

    var model: DeviceSelectionModel?

    override func viewDidLoad() {
        super.viewDidLoad()

        audioDevicePicker.delegate = self
        audioDevicePicker.dataSource = self
        videoDevicePicker.delegate = self
        videoDevicePicker.dataSource = self
        videoFormatPicker.delegate = self
        videoFormatPicker.dataSource = self

        videoPreviewImageView.mirror = model?.shouldMirrorPreview ?? false
        model?.cameraCaptureSource.addVideoSink(sink: videoPreviewImageView)
        model?.cameraCaptureSource.start()
    }

    @IBAction func joinButtonTapped(_: UIButton) {
        guard let model = model else {
            return
        }
        model.cameraCaptureSource.stop()
        model.cameraCaptureSource.removeVideoSink(sink: videoPreviewImageView)
        MeetingModule.shared().deviceSelected(model)
    }
}

extension DeviceSelectionViewController: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent _: Int) -> String? {
        guard let model = model else {
            return nil
        }
        if pickerView == audioDevicePicker {
            if row >= model.audioDevices.count {
                return nil
            }
            return model.audioDevices[row].label
        } else if pickerView == videoDevicePicker {
            if row >= model.videoDevices.count {
                return nil
            }
            return model.videoDevices[row].label
        } else if pickerView == videoFormatPicker {
            let formats = model.supportedVideoFormat[model.selectedVideoDeviceIndex]
            if row >= formats.count {
                return nil
            }
            let format = formats[row]
            return "\(format.width) x \(format.height) @ \(format.maxFrameRate)"
        } else {
            return nil
        }
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent _: Int) {
        guard let model = model else {
            return
        }
        if pickerView == audioDevicePicker {
            if row >= model.audioDevices.count {
                return
            }
            model.selectedAudioDeviceIndex = row
        } else if pickerView == videoDevicePicker {
            if row >= model.videoDevices.count {
                return
            }
            model.selectedVideoDeviceIndex = row
            videoPreviewImageView.mirror = model.shouldMirrorPreview
            videoFormatPicker.reloadAllComponents()
        } else if pickerView == videoFormatPicker {
            let formats = model.supportedVideoFormat[model.selectedVideoDeviceIndex]
            if row >= formats.count {
                return
            }
            model.selectedVideoFormatIndex = row
        } else {
            return
        }
    }
}

extension DeviceSelectionViewController: UIPickerViewDataSource {
    func numberOfComponents(in _: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent _: Int) -> Int {
        guard let model = model else {
            return 0
        }
        if pickerView == audioDevicePicker {
            return model.audioDevices.count
        } else if pickerView == videoDevicePicker {
            return model.videoDevices.count
        } else if pickerView == videoFormatPicker {
            if model.videoDevices.count == 0 {
                return 0
            }
            return model.supportedVideoFormat[model.selectedVideoDeviceIndex].count
        } else {
            return 0
        }
    }
}


//
//  InAppScreenCaptureModel.swift
//  AmazonChimeSDKDemo
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import Foundation

class InAppScreenCaptureModel {
    let logger = ConsoleLogger(name: "InAppScreenCaptureModel")
    let contentShareController: ContentShareController
    var isSharingHandler: ((Bool) -> Void)?

    var isSharing = false {
        willSet(newValue) {
            if newValue == isSharing {
                return
            }
            if newValue {
                inAppScreenCaptureSource?.addCaptureSourceObserver(observer: self)
                inAppScreenCaptureSource?.start()
                isSharingHandler?(true)
            } else {
                inAppScreenCaptureSource?.stop()
                isSharingHandler?(false)
            }
        }
    }

    lazy var inAppScreenCaptureSource: VideoCaptureSource? = {
        if #available(iOS 11.0, *) {
            return InAppScreenCaptureSource(logger: logger)
        }
        return nil
    }()

    init(contentShareController: ContentShareController) {
        self.contentShareController = contentShareController
        self.contentShareController.addContentShareObserver(observer: self)
    }
}

extension InAppScreenCaptureModel: CaptureSourceObserver {
    func captureDidStart() {
        logger.info(msg: "InAppScreenCaptureSource did start")
        let contentShareSource = ContentShareSource()
        contentShareSource.videoSource = inAppScreenCaptureSource
        contentShareController.startContentShare(source: contentShareSource)
    }

    func captureDidStop() {
        logger.info(msg: "InAppScreenCaptureSource did stop")
        contentShareController.stopContentShare()
        inAppScreenCaptureSource?.removeCaptureSourceObserver(observer: self)
    }

    func captureDidFail(error: CaptureSourceError) {
        logger.error(msg: "InAppScreenCaptureSource did fail: \(error.description)")
        isSharing = false
        contentShareController.stopContentShare()
        inAppScreenCaptureSource?.removeCaptureSourceObserver(observer: self)
    }
}

extension InAppScreenCaptureModel: ContentShareObserver {
    func contentShareDidStart() {}

    func contentShareDidStop(status: ContentShareStatus) {
        if isSharing {
            isSharing = false
        }
    }
}