/*
Copyright (C) 2001, 2007 United States Government as represented by
the Administrator of the National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.layers.rpf;

import gov.nasa.worldwind.WWObjectImpl;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.formats.dds.*;
import gov.nasa.worldwind.retrieve.RetrievalPostProcessor;
import gov.nasa.worldwind.retrieve.Retriever;
import gov.nasa.worldwind.util.Logging;

import java.awt.image.BufferedImage;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;

/**
 * @author dcollins
 * @version $Id: RPFRetriever.java 8942 2009-02-21 00:34:18Z dcollins $
 */
class RPFRetriever extends WWObjectImpl implements Retriever
{
    private volatile ByteBuffer byteBuffer;
    private volatile int contentLength = 0;
    private AtomicInteger contentLengthRead = new AtomicInteger(0);
    private volatile String state = RETRIEVER_STATE_NOT_STARTED;
    private volatile String contentType;
    private long submitTime;
    private long beginTime;
    private long endTime;
    private int connectTimeout = -1;
    private int readTimeout = -1;
    private int staleRequestLimit = -1;

    private final RPFGenerator.RPFServiceInstance service;
    private final URL url;
    private final RetrievalPostProcessor postProcessor;
    private int responseCode;

    public static final int RESPONSE_CODE_OK = 1;
    public static final int RESPONSE_CODE_NO_CONTENT = 2;

    public RPFRetriever(RPFGenerator.RPFServiceInstance service, URL url, RetrievalPostProcessor postProcessor)
    {
        if (service == null)
        {
            String message = "Service is null";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (url == null)
        {
            String message = Logging.getMessage("nullValue.URLIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (postProcessor == null)
        {
            String message = Logging.getMessage("nullValue.PostProcessorIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.service = service;
        this.url = url;
        this.postProcessor = postProcessor;
    }

    public final ByteBuffer getBuffer()
    {
        return this.byteBuffer;
    }

    public final int getContentLength()
    {
        return this.contentLength;
    }

    public final int getContentLengthRead()
    {
        return this.contentLengthRead.get();
    }

    protected void setContentLengthRead(int length)
    {
        this.contentLengthRead.set(length);
    }

    public final String getName()
    {
        return this.url.toString();
    }

    public final String getState()
    {
        return this.state;
    }

    public final String getContentType()
    {
        return this.contentType;
    }

    public long getSubmitTime()
    {
        return this.submitTime;
    }

    public void setSubmitTime(long submitTime)
    {
        this.submitTime = submitTime;
    }

    public long getBeginTime()
    {
        return this.beginTime;
    }

    public void setBeginTime(long beginTime)
    {
        this.beginTime = beginTime;
    }

    public long getEndTime()
    {
        return this.endTime;
    }

    public void setEndTime(long endTime)
    {
        this.endTime = endTime;
    }

    public int getConnectTimeout()
    {
        return this.connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout)
    {
        this.connectTimeout = connectTimeout;
    }

    public int getReadTimeout()
    {
        return this.readTimeout;
    }

    public void setReadTimeout(int readTimeout)
    {
        this.readTimeout = readTimeout;
    }

    public int getStaleRequestLimit()
    {
        return this.staleRequestLimit;
    }

    public void setStaleRequestLimit(int staleRequestLimit)
    {
        this.staleRequestLimit = staleRequestLimit;
    }

    public final RPFGenerator.RPFServiceInstance getService()
    {
        return this.service;
    }

    public final URL getURL()
    {
        return this.url;
    }

    public final RetrievalPostProcessor getPostProcessor()
    {
        return this.postProcessor;
    }

    public int getResponseCode()
    {
        return this.responseCode;
    }

    public final Retriever call() throws Exception
    {
        if (interrupted())
            return this;

        try
        {
            setState(RETRIEVER_STATE_STARTED);

            // Simulate connected state.
            if (!interrupted())
                setState(RETRIEVER_STATE_CONNECTING);

            if (!interrupted())
            {
                setState(RETRIEVER_STATE_READING);
                this.byteBuffer = read();
            }

            if (!interrupted())
                setState(RETRIEVER_STATE_SUCCESSFUL);
        }
        catch (Exception e)
        {
            setState(RETRIEVER_STATE_ERROR);
            Logging.logger().log(Level.SEVERE,
                    Logging.getMessage("URLRetriever.ErrorAttemptingToRetrieve", this.url.toString()), e);
        }
        finally
        {
            end();
        }

        return this;
    }

    private void setState(String state)
    {
        String oldState = this.state;
        this.state = state;
        this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state);
    }

    private boolean interrupted()
    {
        if (Thread.currentThread().isInterrupted())
        {
            setState(RETRIEVER_STATE_INTERRUPTED);
            String message = Logging.getMessage("URLRetriever.RetrievalInterruptedFor", this.url.toString());
            Logging.logger().fine(message);
            return true;
        }
        return false;
    }

    private void end() throws Exception
    {
        try
        {
            if (this.postProcessor != null)
            {
                this.byteBuffer = this.postProcessor.run(this);
            }
        }
        catch (Exception e)
        {
            setState(RETRIEVER_STATE_ERROR);
            Logging.logger().log(Level.SEVERE,
                Logging.getMessage("URLRetriever.ErrorPostProcessing", this.url.toString()), e);
            throw e;
        }
    }

    private ByteBuffer read() throws Exception
    {
        ByteBuffer buffer = this.doRead(this.service, this.url);
        if (buffer == null)
            this.contentLength = 0;
        return buffer;
    }

    protected ByteBuffer doRead(RPFGenerator.RPFServiceInstance service, URL url) throws Exception
    {
        ByteBuffer buffer = null;

        BufferedImage bufferedImage = service.serviceRequest(url);
        if (bufferedImage != null)
        {
            // TODO: format parameter should determine image type
            buffer = DDSCompressor.compressImage(bufferedImage);
            if (buffer != null)
            {
                int length = buffer.limit();
                this.contentType = "image/dds";
                this.contentLength = length;
                setContentLengthRead(length);
            }
        }

        // TODO: service should provide response code
        this.responseCode = buffer != null ? RESPONSE_CODE_OK : RESPONSE_CODE_NO_CONTENT;

        return buffer;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final RPFRetriever that = (RPFRetriever) o;
        
        // Retrievers are considered identical if they are for the same URL. This convention is used by the
        // retrieval service to filter out duplicate retreival requests.
        return !(url != null ? !url.toString().contentEquals(that.url.toString()) : that.url != null);
    }

    @Override
    public int hashCode()
    {
        int result;
        result = (url != null ? url.hashCode() : 0);
        return result;
    }

    @Override
    public String toString()
    {
        return this.getName() != null ? this.getName() : super.toString();
    }
}