package org.skywalking.apm.collector.actor;

import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import org.skywalking.apm.collector.queue.EndOfBatchCommand;
import org.skywalking.apm.collector.queue.MessageHolder;

/**
 * The <code>AbstractLocalAsyncWorker</code> implementations represent workers,
 * which receive local asynchronous message.
 *
 * @author pengys5
 * @since v3.0-2017
 */
public abstract class AbstractLocalAsyncWorker extends AbstractLocalWorker {

    /**
     * Construct an <code>AbstractLocalAsyncWorker</code> with the worker role and context.
     *
     * @param role           The responsibility of worker in cluster, more than one workers can have same responsibility which use
     *                       to provide load balancing ability.
     * @param clusterContext See {@link ClusterWorkerContext}
     * @param selfContext    See {@link LocalWorkerContext}
     */
    public AbstractLocalAsyncWorker(Role role, ClusterWorkerContext clusterContext, LocalWorkerContext selfContext) {
        super(role, clusterContext, selfContext);
    }

    /**
     * The asynchronous worker always use to persistence data into db, this is the end of the streaming,
     * so usually no need to create the next worker instance at the time of this worker instance create.
     *
     * @throws ProviderNotFoundException When worker provider not found, it will be throw this exception.
     */
    @Override
    public void preStart() throws ProviderNotFoundException {
    }

    /**
     * Receive message
     *
     * @param message The persistence data or metric data.
     * @throws Exception The Exception happen in {@link #onWork(Object)}
     */
    final public void allocateJob(Object message) throws Exception {
        onWork(message);
    }

    /**
     * The data process logic in this method.
     *
     * @param message Cast the message object to a expect subclass.
     * @throws Exception Don't handle the exception, throw it.
     */
    protected abstract void onWork(Object message) throws Exception;

    static class WorkerWithDisruptor implements EventHandler<MessageHolder> {

        private RingBuffer<MessageHolder> ringBuffer;
        private AbstractLocalAsyncWorker asyncWorker;

        WorkerWithDisruptor(RingBuffer<MessageHolder> ringBuffer, AbstractLocalAsyncWorker asyncWorker) {
            this.ringBuffer = ringBuffer;
            this.asyncWorker = asyncWorker;
        }

        /**
         * Receive the message from disruptor, when message in disruptor is empty, then send the cached data
         * to the next workers.
         *
         * @param event      published to the {@link RingBuffer}
         * @param sequence   of the event being processed
         * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer}
         */
        public void onEvent(MessageHolder event, long sequence, boolean endOfBatch) {
            try {
                Object message = event.getMessage();
                event.reset();

                asyncWorker.allocateJob(message);
                if (endOfBatch) {
                    asyncWorker.allocateJob(new EndOfBatchCommand());
                }
            } catch (Exception e) {
                asyncWorker.saveException(e);
            }
        }

        /**
         * Push the message into disruptor ring buffer.
         *
         * @param message of the data to process.
         * @throws Exception not used.
         */
        public void tell(Object message) throws Exception {
            long sequence = ringBuffer.next();
            try {
                ringBuffer.get(sequence).setMessage(message);
            } finally {
                ringBuffer.publish(sequence);
            }
        }
    }
}
