/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.node;

import freenet.client.FetchContext;
import freenet.client.FetchException;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.async.ClientContext;
import freenet.client.async.ClientGetState;
import freenet.client.async.ClientRequestScheduler;
import freenet.client.async.ClientRequester;
import freenet.client.async.PersistentJob;
import freenet.client.async.SimpleSingleFileFetcher;
import freenet.client.async.WantsCooldownCallback;
import freenet.keys.ClientKey;
import freenet.keys.Key;
import freenet.support.Logger;
import freenet.support.io.NativeThread;

/**
 * A low-level key fetch which can be sent immediately. @see SendableRequest
 * 
 * WARNING: Changing non-transient members on classes that are Serializable can result in 
 * restarting downloads or losing uploads. (Some children of this class are actually stored)
 */
public abstract class SendableGet extends BaseSendableGet {

    private static final long serialVersionUID = 1L;
    /** Parent BaseClientGetter. Required for schedulers. */
	public final ClientRequester parent;
	
	/** Get a numbered key to fetch. */
	public abstract ClientKey getKey(SendableRequestItem token);
	
	@Override
	public Key getNodeKey(SendableRequestItem token) {
		ClientKey key = getKey(token);
		if(key == null) return null;
		return key.getNodeKey(true);
	}
	
	/**
	 * What keys are we interested in? For purposes of checking the datastore.
	 * This is in SendableGet, *not* KeyListener, in order to deal with it in
	 * smaller chunks.
	 */
	public abstract Key[] listKeys();

	/** Get the fetch context (settings) object. */
	public abstract FetchContext getContext();
	
	/** Called when/if the low-level request fails. */
	public abstract void onFailure(LowLevelGetException e, SendableRequestItem token, ClientContext context);
	
	// Implementation

	public SendableGet(ClientRequester parent, boolean realTimeFlag) {
		super(parent.persistent(), realTimeFlag);
		this.parent = parent;
	}
	
	static final SendableGetRequestSender sender = new SendableGetRequestSender();
	
	@Override
	public SendableRequestSender getSender(ClientContext context) {
		return sender;
	}
	
	@Override
	public ClientRequestScheduler getScheduler(ClientContext context) {
		if(isSSK())
			return context.getSskFetchScheduler(realTimeFlag);
		else
			return context.getChkFetchScheduler(realTimeFlag);
	}

	/**
	 * Get the time at which the key specified by the given token will wake up from the 
	 * cooldown queue.
	 * @param token
	 * @return
	 */
	public abstract long getCooldownWakeup(SendableRequestItem token, ClientContext context);
	
	/**
	 * An internal error occurred, effecting this SendableGet, independantly of any ChosenBlock's.
	 */
	@Override
	public void internalError(final Throwable t, final RequestScheduler sched, ClientContext context, boolean persistent) {
		Logger.error(this, "Internal error on "+this+" : "+t, t);
		sched.callFailure(this, new LowLevelGetException(LowLevelGetException.INTERNAL_ERROR, t.getMessage(), t), NativeThread.MAX_PRIORITY, persistent);
	}

	@Override
	public final boolean isInsert() {
		return false;
	}

	@Override
	public void unregister(ClientContext context, short oldPrio) {
		super.unregister(context, oldPrio);
		ClientRequestScheduler scheduler = getScheduler(context);
		context.checker.removeRequest(this, persistent, context, oldPrio == -1 ? getPriorityClass() : oldPrio);
	}
	
	public static FetchException translateException(LowLevelGetException e) {
	    switch(e.code) {
	    case LowLevelGetException.DATA_NOT_FOUND:
	    case LowLevelGetException.DATA_NOT_FOUND_IN_STORE:
	        return new FetchException(FetchExceptionMode.DATA_NOT_FOUND);
	    case LowLevelGetException.RECENTLY_FAILED:
	        return new FetchException(FetchExceptionMode.RECENTLY_FAILED);
	    case LowLevelGetException.DECODE_FAILED:
	        return new FetchException(FetchExceptionMode.BLOCK_DECODE_ERROR);
	    case LowLevelGetException.INTERNAL_ERROR:
	        return new FetchException(FetchExceptionMode.INTERNAL_ERROR);
	    case LowLevelGetException.REJECTED_OVERLOAD:
	        return new FetchException(FetchExceptionMode.REJECTED_OVERLOAD);
	    case LowLevelGetException.ROUTE_NOT_FOUND:
	        return new FetchException(FetchExceptionMode.ROUTE_NOT_FOUND);
	    case LowLevelGetException.TRANSFER_FAILED:
	        return new FetchException(FetchExceptionMode.TRANSFER_FAILED);
	    case LowLevelGetException.VERIFY_FAILED:
	        return new FetchException(FetchExceptionMode.BLOCK_DECODE_ERROR);
	    case LowLevelGetException.CANCELLED:
	        return new FetchException(FetchExceptionMode.CANCELLED);
	    default:
	        Logger.error(SimpleSingleFileFetcher.class, "Unknown LowLevelGetException code: "+e.code);
	        return new FetchException(FetchExceptionMode.INTERNAL_ERROR, "Unknown error code: "+e.code);
	    }
	}
	
    @Override
    public boolean reduceWakeupTime(final long wakeupTime, ClientContext context) {
        boolean ret = super.reduceWakeupTime(wakeupTime, context);
        if(this.parent instanceof WantsCooldownCallback) {
            context.getJobRunner(persistent).queueNormalOrDrop(new PersistentJob() {

                @Override
                public boolean run(ClientContext context) {
                    ((WantsCooldownCallback)parent).enterCooldown(getClientGetState(), wakeupTime, context);
                    return false;
                }
                
            });
        }
        return ret;
    }
    
    @Override
    public void clearWakeupTime(ClientContext context) {
        super.clearWakeupTime(context);
        if(this.parent instanceof WantsCooldownCallback) {
            context.getJobRunner(persistent).queueNormalOrDrop(new PersistentJob() {

                @Override
                public boolean run(ClientContext context) {
                    ((WantsCooldownCallback)parent).clearCooldown(getClientGetState());
                    return false;
                }
                
            });
        }
    }

    protected abstract ClientGetState getClientGetState();

}
