//
// Copyright (c) ZeroC, Inc. All rights reserved.
//
import Ice
import PromiseKit
import TestCommon

class HelloI: Hello {
    func sayHello(current _: Ice.Current) {}
}

class TestI: TestIntf {
    var _adapter1: Ice.ObjectAdapter
    var _adapter2: Ice.ObjectAdapter
    var _registry: ServerLocatorRegistry

    init(adapter1: Ice.ObjectAdapter,
         adapter2: Ice.ObjectAdapter,
         registry: ServerLocatorRegistry) throws {
        _adapter1 = adapter1
        _adapter2 = adapter2
        _registry = registry

        try _registry.addObject(_adapter1.add(servant: HelloDisp(HelloI()), id: Ice.stringToIdentity("hello")))
    }

    func shutdown(current _: Ice.Current) throws {
        _adapter1.getCommunicator().shutdown()
    }

    func getHello(current _: Ice.Current) throws -> HelloPrx? {
        return try uncheckedCast(prx: _adapter1.createIndirectProxy(Ice.stringToIdentity("hello")),
                                 type: HelloPrx.self)
    }

    func getReplicatedHello(current _: Ice.Current) throws -> HelloPrx? {
        return try uncheckedCast(prx: _adapter1.createProxy(Ice.stringToIdentity("hello")),
                                 type: HelloPrx.self)
    }

    func migrateHello(current _: Ice.Current) throws {
        let id = try Ice.stringToIdentity("hello")
        do {
            try _registry.addObject(_adapter2.add(servant: _adapter1.remove(id), id: id))
        } catch is Ice.NotRegisteredException {
            try _registry.addObject(_adapter1.add(servant: _adapter2.remove(id), id: id))
        }
    }
}

class ServerManagerI: ServerManager {
    var _registry: ServerLocatorRegistry
    var _helper: TestHelper
    var _communicators = [Ice.Communicator]()
    var _nextPort: Int32 = 1

    init(registry: ServerLocatorRegistry, helper: TestHelper) {
        _registry = registry
        _helper = helper
    }

    func startServer(current _: Ice.Current) throws {
        for c in _communicators {
            c.waitForShutdown()
            c.destroy()
        }
        _communicators.removeAll()

        //
        // Simulate a server: create a new communicator and object
        // adapter. The object adapter is started on a system allocated
        // port. The configuration used here contains the Ice.Locator
        // configuration variable. The new object adapter will register
        // its endpoints with the locator and create references containing
        // the adapter id instead of the endpoints.
        //
        var initData = Ice.InitializationData()
        let properties = _helper.communicator().getProperties().clone()
        properties.setProperty(key: "TestAdapter.AdapterId", value: "TestAdapter")
        properties.setProperty(key: "TestAdapter.ReplicaGroupId", value: "ReplicatedAdapter")
        properties.setProperty(key: "TestAdapter2.AdapterId", value: "TestAdapter2")
        initData.properties = properties

        let serverCommunicator = try _helper.initialize(initData)
        _communicators.append(serverCommunicator)

        //
        // Use fixed port to ensure that OA re-activation doesn't re-use previous port from
        // another OA(e.g.: TestAdapter2 is re-activated using port of TestAdapter).
        //
        for i in 1 ... 10 {
            var adapter: Ice.ObjectAdapter!
            var adapter2: Ice.ObjectAdapter!

            do {
                _nextPort += 1
                serverCommunicator.getProperties().setProperty(key: "TestAdapter.Endpoints",
                                                               value: _helper.getTestEndpoint(num: _nextPort))

                _nextPort += 1
                serverCommunicator.getProperties().setProperty(key: "TestAdapter2.Endpoints",
                                                               value: _helper.getTestEndpoint(num: _nextPort))

                adapter = try serverCommunicator.createObjectAdapter("TestAdapter")
                adapter2 = try serverCommunicator.createObjectAdapter("TestAdapter2")

                let locator = try serverCommunicator.stringToProxy("locator:\(_helper.getTestEndpoint(num: 0))")!
                try adapter.setLocator(uncheckedCast(prx: locator, type: Ice.LocatorPrx.self))
                try adapter2.setLocator(uncheckedCast(prx: locator, type: Ice.LocatorPrx.self))

                let object = try TestI(adapter1: adapter, adapter2: adapter2, registry: _registry)
                try _registry.addObject(adapter.add(servant: TestIntfDisp(object), id: Ice.stringToIdentity("test")))
                try _registry.addObject(adapter.add(servant: TestIntfDisp(object), id: Ice.stringToIdentity("test2")))
                _ = try adapter.add(servant: TestIntfDisp(object), id: Ice.stringToIdentity("test3"))

                try adapter.activate()
                try adapter2.activate()
                break
            } catch let ex as Ice.SocketException {
                if i == 10 {
                    throw ex
                }

                // Retry, if OA creation fails with EADDRINUSE(this can occur when running with JS web
                // browser clients if the driver uses ports in the same range as this test, ICE-8148)
                if adapter != nil {
                    adapter.destroy()
                }

                if adapter2 != nil {
                    adapter2.destroy()
                }
            }
        }
    }

    func shutdown(current: Ice.Current) throws {
        for c in _communicators {
            c.destroy()
        }
        _communicators.removeAll()
        current.adapter!.getCommunicator().shutdown()
    }
}

class ServerLocator: TestLocator {
    var _registry: ServerLocatorRegistry
    var _registryPrx: Ice.LocatorRegistryPrx
    var _requestCount: Int32

    init(registry: ServerLocatorRegistry, registryPrx: Ice.LocatorRegistryPrx) {
        _registry = registry
        _registryPrx = registryPrx
        _requestCount = 0
    }

    func findAdapterByIdAsync(id: String, current: Ice.Current) -> Promise<ObjectPrx?> {
        _requestCount += 1
        if id == "TestAdapter10" || id == "TestAdapter10-2" {
            precondition(current.encoding == Ice.Encoding_1_0)
            return Promise<ObjectPrx?> { seal in
                do {
                    try seal.fulfill(_registry.getAdapter("TestAdapter"))
                } catch {
                    seal.reject(error)
                }
            }
        } else {
            // We add a small delay to make sure locator request queuing gets tested when
            // running the test on a fast machine
            Thread.sleep(forTimeInterval: 0.1)
            return Promise<ObjectPrx?> { seal in
                do {
                    try seal.fulfill(_registry.getAdapter(id))
                } catch {
                    seal.reject(error)
                }
            }
        }
    }

    func findObjectByIdAsync(id: Ice.Identity, current _: Ice.Current) -> Promise<ObjectPrx?> {
        _requestCount += 1
        // We add a small delay to make sure locator request queuing gets tested when
        // running the test on a fast machine
        Thread.sleep(forTimeInterval: 0.1)
        return Promise<ObjectPrx?> { seal in
            do {
                try seal.fulfill(_registry.getObject(id))
            } catch {
                seal.reject(error)
            }
        }
    }

    func getRegistry(current _: Ice.Current) throws -> Ice.LocatorRegistryPrx? {
        return _registryPrx
    }

    func getRequestCount(current _: Ice.Current) throws -> Int32 {
        return _requestCount
    }
}

class ServerLocatorRegistry: TestLocatorRegistry {
    var _adapters = [String: Ice.ObjectPrx]()
    var _objects = [Ice.Identity: Ice.ObjectPrx]()
    var _lock = os_unfair_lock()

    func setAdapterDirectProxyAsync(id: String, proxy: ObjectPrx?, current _: Current) -> Promise<Void> {
        return Promise<Void> { seal in
            withLock(&_lock) {
                if let obj = proxy {
                    self._adapters[id] = obj
                } else {
                    self._adapters.removeValue(forKey: id)
                }
            }
            seal.fulfill(())
        }
    }

    func setReplicatedAdapterDirectProxyAsync(adapterId adapter: String,
                                              replicaGroupId replica: String,
                                              p: Ice.ObjectPrx?,
                                              current _: Ice.Current) -> Promise<Void> {
        return Promise<Void> { seal in
            withLock(&_lock) {
                if let obj = p {
                    _adapters[adapter] = obj
                    _adapters[replica] = obj
                } else {
                    _adapters.removeValue(forKey: adapter)
                    _adapters.removeValue(forKey: replica)
                }
            }
            seal.fulfill(())
        }
    }

    func setServerProcessProxyAsync(id _: String, proxy _: Ice.ProcessPrx?, current _: Ice.Current) -> Promise<Void> {
        return Promise<Void> { seal in
            seal.fulfill(())
        }
    }

    func addObject(_ obj: Ice.ObjectPrx?) {
        withLock(&_lock) {
            _objects[obj!.ice_getIdentity()] = obj
        }
    }

    func addObject(obj: Ice.ObjectPrx?, current _: Ice.Current) throws {
        addObject(obj)
    }

    func getAdapter(_ id: String) throws -> Ice.ObjectPrx {
        guard let obj = _adapters[id] else {
            throw Ice.AdapterNotFoundException()
        }
        return obj
    }

    func getObject(_ id: Ice.Identity) throws -> Ice.ObjectPrx {
        guard let obj = _objects[id] else {
            throw Ice.ObjectNotFoundException()
        }
        return obj
    }
}
