/**
 * Copyright 2024 Beijing Baolande Software Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Runtime Library Exception to the Apache 2.0 License:
 *
 * As an exception, if you use this Software to compile your source code and
 * portions of this Software are embedded into the binary product as a result,
 * you may redistribute such product without providing attribution as would
 * otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
 */
package activemq4cj.client

public class ActiveMQQueueBrowser <: Iterator<CJMSMessage> & QueueBrowser {
    private let session: ActiveMQSession
    private let destination: ActiveMQDestination
    private let consumerId: ConsumerId
    private let selector: ?String

    private var consumer: ?ActiveMQMessageConsumer = None
    private var closed: Bool = false
    private var mutex: Mutex = Mutex()
    private let condition: Condition = synchronized(mutex) {
        mutex.condition()
    }
    private let dispatchAsync: Bool
    protected var browseDone: Bool = false

    protected init(session: ActiveMQSession, consumerId: ConsumerId, destination: ActiveMQDestination,
        selector: ?String, dispatchAsync: Bool) {
        if (destination.name.isNone()) {
            throw InvalidDestinationException("The destination object was not given a physical name")
        }

        this.session = session
        this.consumerId = consumerId
        this.destination = destination
        this.selector = selector
        this.dispatchAsync = dispatchAsync
        this.consumer = createConsumer()
    }

    private func createConsumer(): ActiveMQMessageConsumer {
        browseDone = false

        var prefetchPolicy: ActiveMQPrefetchPolicy = session.connection.prefetchPolicy
        // 创建用于浏览消息的消费者
        let browseConsumer = BrowsingMessageConsumer(this, session, consumerId, destination, None, selector,
            prefetchPolicy.queueBrowserPrefetch, prefetchPolicy.maximumPendingMessageLimit, false, true, dispatchAsync)

        //加入消息调度
        session.addConsumer(browseConsumer)
        return browseConsumer
    }

    private func destroyConsumer(): Unit {
        if (let Some(consumer) <- this.consumer) {
            if (session.getTransacted()) {
                if (let Some(transactionContext) <- session.transactionContext) {
                    if (transactionContext.isInLocalTransaction()) {
                        session.commit()
                    }
                }
            }
            consumer.close()
            this.consumer = None
        }
    }

    /**
     * 通过迭代来浏览消息
     */
    public override func next(): Option<CJMSMessage> {
        while (true) {
            synchronized(mutex) {
                if (let Some(consumer) <- consumer) {
                    try {
                        var answer = consumer.receiveNoWait()
                        if (answer.isSome()) {
                            return answer
                        }
                    } catch (ex: CJMSException) {
                        return None
                    }
                } else {
                    return None
                }
            }

            // 当dispatch方法接收到message为None的MessageDispatch,browseDone置为true,next返回None
            if (browseDone || !session.isRunning()) {
                destroyConsumer()
                return None
            }

            waitForMessage()
        }
        return None
    }

    /**
     * 通过发送MessagePull来继续获取消息
     */
    private func waitForMessage(): Unit {
        if (let Some(consumer) <- consumer) {
            try {
                consumer.sendPullCommand(Duration.millisecond * -1)
                synchronized(mutex) {
                    condition.wait(timeout: Duration.millisecond * 2000)
                }
            } catch (ex: CJMSException) {
            }
        }
    }

    public override func isClosed(): Bool {
        return closed
    }

    public override func close(): Unit {
        browseDone = true
        destroyConsumer()
        closed = true
    }

    protected func notifyMessageAvailable(): Unit {
        synchronized(mutex) {
            condition.notifyAll()
        }
    }

    public prop queue: Queue {
        get() {
            if (let Some(queue) <- (destination as Queue)) {
                return queue
            }
            throw InvalidDestinationException("The destination is not a Queue")
        }
    }

    public prop messageSelector: ?String {
        get() {
            return this.selector
        }
    }
}

public class BrowsingMessageConsumer <: ActiveMQMessageConsumer {
    private let parent: ActiveMQQueueBrowser

    public init(parent: ActiveMQQueueBrowser, session: ActiveMQSession, id: ConsumerId,
        destination: ActiveMQDestination, name: ?String, selector: ?String, prefetch: Int32,
        maxPendingMessageCount: Int32, noLocal: Bool, browser: Bool, dispatchAsync: Bool) {
        super(session, id, destination, name, selector, prefetch, maxPendingMessageCount, noLocal, browser,
            dispatchAsync, None)
        this.parent = parent
    }

    /**
     * 重写dispatch方法,当message为None时设置browseDone为true,此时next方法退出,消息浏览结束
     */
    public override func dispatch(md: MessageDispatch): Unit {
        if (md.message.isNone()) {
            parent.browseDone = true
        } else {
            super.dispatch(md)
        }

        parent.notifyMessageAvailable()
    }
}