package net.redborder.cep.sources;

import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.dsl.Disruptor;
import net.redborder.cep.sources.disruptor.EventProducer;
import net.redborder.cep.sources.disruptor.MapEvent;
import net.redborder.cep.sources.disruptor.MapEventFactory;
import net.redborder.cep.sources.parsers.ParsersManager;
import net.redborder.cep.util.ConfigData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Map;
import java.util.concurrent.Executors;

/**
 * This abstract class serves as a parent class of the different sources implementations.
 * It implements some methods to send events to the ring buffer, and defines an interface
 * that the class children must implement in order to add a new source.
 *
 * @see net.redborder.cep.sources.kafka.KafkaSource
 */

public abstract class Source {
    private static final Logger log = LogManager.getLogger(Source.class);

    // The instance of disruptor that will consume the events that are produced by this source
    public EventProducer eventProducer;

    // The instance of the parsers manager that will serve parser instances in order to parse
    // the events that come from this source
    public ParsersManager parsersManager;

    // The list of properties associated with this instance on the config file
    public Map<String, Object> properties;

    /**
     * Create a new source.
     * <p>This method will prepare the instance with some needed variables
     * in order to be started later with the start method (implemented by children).
     *
     * @param parsersManager Instance of ParserManager that will serve parsers to this source instance
     * @param eventHandler Instance of EventHandler that will receive the events generated by this source instance
     * @param properties Map of properties associated with this source
     */

    public Source(ParsersManager parsersManager, EventHandler eventHandler, Map<String, Object> properties) {
        // Save the references for later use
        this.parsersManager = parsersManager;
        this.properties = properties;

        // Create the ring buffer for this topic and start it
        Disruptor<MapEvent> disruptor = new Disruptor<>(new MapEventFactory(), ConfigData.getRingBufferSize(), Executors.newCachedThreadPool());
        disruptor.handleEventsWith(eventHandler);
        disruptor.start();

        // Create the event producer that will receive the events produced by
        // this source instance
        eventProducer = new EventProducer(disruptor.getRingBuffer());
        prepare();
    }

    /**
     * Sends a new string message to the stream.
     * <p>The param msg should be a string that will be parsed by the parser manager. The parser manager
     * stores a reference of the parser that must be used with each stream, so when a string message
     * is sent, the parser manager parses it to convert it into a map object.
     * <p>This method should be called when the source wants to generate a message to the stream.
     *
     * @param streamName The stream that will receive the message
     * @param msg The string message that will be sent
     */

    public void send(String streamName, String msg) {
        Map<String, Object> data = parsersManager.parse(streamName, msg);
        eventProducer.putData(streamName, data);
    }

    /**
     * Sends a new map message to the producer.
     *
     * @param streamName The stream that will receive the message
     * @param data The message that will be sent
     */

    public void send(String streamName, Map<String, Object> data) {
        eventProducer.putData(streamName, data);
    }

    /**
     * Gets a property from the source properties.
     * <p>The source properties are a series of optional properties that are specified on the config file
     * and associated with each stream.
     *
     * @param propertyName The property that will be returned
     * @return The value associated with the property name specified
     */

    public Object getProperty(String propertyName){
        return properties.get(propertyName);
    }

    /**
     * This method is called by the SourceManager to add one or more new streams to the source.
     * <p>The source implementation must process the streams and do whatever it needs to do to add
     * that stream to the source. For example, you could create a new consumer thread for each stream
     * received, open a new HTTP connection to some external third-party API, etc.
     * <p>The streams that will be passed to the function addStreams of the implementations are
     * specified on the config file by the user.
     *
     * @param streamName One or more streams that has been added in the source.
     * @see SourcesManager
     */

    public abstract void addStreams(String ... streamName);

    /**
     * This method is called automatically by the Source constructor after finishing, so your
     * implementation can use it to prepare for the following start. For example, you could use it
     * to establish a connection, open some resource, etc.
     */

    public abstract void prepare();

    /**
     * This method will be called when the Source should be started, so after this call, the source
     * must start to generate messages to the producers using the send methods provided by this class.
     */

    public abstract void start();

    /**
     * This method will be called when the Source must stop, in order to close connections and
     * release resources that will no longer be useful.
     */

    public abstract void shutdown();
}