/**
 * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
 * All rights reserved. Use is subject to license terms. See LICENSE.TXT
 */
package org.diirt.datasource.timecache;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.diirt.datasource.timecache.source.DataSource;
import org.diirt.datasource.timecache.util.CacheHelper;
import org.diirt.datasource.timecache.util.IntervalsList;
import org.diirt.util.time.TimeInterval;

/**
 * Retrieves chunks from the specified {@link DataSource}, channel name and
 * {@link TimeInterval}. Polls chunks from the source until the
 * {@link Timestamp} of the last received {@link Data} is superior to the end of
 * the defined {@link TimeInterval}.
 * @author Fred Arnaud (Sopra Group) - ITER
 */
public class DataRequestThread extends Thread {

    private static AtomicInteger idCounter = new AtomicInteger(0);
    private final Integer requestID;

    private final String channelName;
    private final DataSource source;
    private TimeInterval interval;
    private Instant lastReceived;

    private List<DataRequestListener> listeners;

    public DataRequestThread(String channelName, DataSource source,
            TimeInterval interval) throws Exception {
        if (channelName == null || channelName.isEmpty() || source == null
                || interval == null)
            throw new Exception("null or empty argument not allowed");
        this.requestID = idCounter.getAndIncrement();
        this.listeners = new ArrayList<DataRequestListener>();
        this.channelName = channelName;
        this.source = source;
        this.interval = CacheHelper.arrange(interval);
        this.lastReceived = this.interval.getStart();
    }

    /** {@inheritDoc} */
    @Override
    public void run() {
        if (interval.getStart() == null) {
            notifyComplete();
            return;
        }
        DataChunk currentChunk = source.getData(channelName, interval.getStart());
        boolean process = true;
        while (process) {
            if (currentChunk == null || currentChunk.isEmpty()
                    || !CacheHelper.intersects(interval, currentChunk.getInterval())) {
                process = false;
                break;
            } else {
                lastReceived = currentChunk.getInterval().getEnd();
                notifyNewData(currentChunk);
                if (!currentChunk.isFull() || !interval.contains(lastReceived)) {
                    process = false;
                    break;
                }
            }
            currentChunk = source.getData(channelName, lastReceived.plus(IntervalsList.minDuration));
        }
        notifyComplete();
    }

    // Notify the listeners that a new chunk is available
    private void notifyNewData(DataChunk chunk) {
        for (DataRequestListener l : listeners)
            l.newData(chunk, this);
    }

    // Notify the listeners that the thread has finished requesting samples
    private void notifyComplete() {
        for (DataRequestListener l : listeners)
            l.intervalComplete(this);
    }

    /** Add a {@link DataRequestListener}. */
    public void addListener(DataRequestListener l) {
        if (l != null)
            listeners.add(l);
    }

    /** Remove a {@link DataRequestListener}. */
    public void removeListener(DataRequestListener l) {
        if (l != null)
            listeners.remove(l);
    }

    public TimeInterval getInterval() {
        return interval;
    }

    public void setInterval(TimeInterval interval) {
        this.interval = interval;
    }

    public String getChannelName() {
        return channelName;
    }

    public DataSource getSource() {
        return source;
    }

    public Instant getLastReceived() {
        return lastReceived;
    }

    public Integer getRequestID() {
        return requestID;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((requestID == null) ? 0 : requestID.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DataRequestThread other = (DataRequestThread) obj;
        if (requestID == null) {
            if (other.requestID != null)
                return false;
        } else if (!requestID.equals(other.requestID))
            return false;
        return true;
    }

}
