package freenet.client.async;

import freenet.client.FetchContext;
import freenet.client.FetchException;
import freenet.client.async.SplitFileFetcherStorage.MyKey;
import freenet.keys.ClientKey;
import freenet.keys.Key;
import freenet.node.KeysFetchingLocally;
import freenet.node.LowLevelGetException;
import freenet.node.RequestClient;
import freenet.node.SendableGet;
import freenet.node.SendableRequestItem;
import freenet.support.Logger;

/** Actually does the splitfile fetch. Only one fetcher object for an entire splitfile.
 * 
 * PERSISTENCE: Not persistent, recreated on startup by SplitFileFetcher. */
@SuppressWarnings("serial")
public class SplitFileFetcherGet extends SendableGet implements HasKeyListener {
    
    private static volatile boolean logMINOR;
    static {
        Logger.registerClass(SplitFileFetcherGet.class);
    }

    final SplitFileFetcher parent;
    final SplitFileFetcherStorage storage;

    public SplitFileFetcherGet(SplitFileFetcher fetcher, SplitFileFetcherStorage storage) {
        super(fetcher.parent, fetcher.realTimeFlag);
        this.parent = fetcher;
        this.storage = storage;
    }

    @Override
    public ClientKey getKey(SendableRequestItem token) {
        MyKey key = (MyKey) token;
        if(key.get != storage) throw new IllegalArgumentException();
        return storage.getKey(key);
    }

    @Override
    public Key[] listKeys() {
        return storage.listUnfetchedKeys();
    }

    @Override
    public FetchContext getContext() {
        return parent.blockFetchContext;
    }

    @Override
    public void onFailure(LowLevelGetException e, SendableRequestItem token,
            ClientContext context) {
        FetchException fe = translateException(e);
        if(fe.isDefinitelyFatal()) {
            // If the error is definitely-fatal it means there is either a serious local problem
            // or the inserted data was corrupt. So we fail the entire splitfile immediately.
            // We don't track which blocks have fatally failed.
            if(logMINOR) Logger.minor(this, "Fatal failure: "+fe+" for "+token);
            parent.fail(fe);
        } else {
            MyKey key = (MyKey) token;
            if(key.get != storage) throw new IllegalArgumentException();
            storage.onFailure(key, fe);
        }
    }
    
    @Override
    public long getWakeupTime(ClientContext context, long now) {
        long wakeTime = storage.getCooldownWakeupTime(now);
        if(wakeTime == 0) return 0;
        return wakeTime;
    }

    @Override
    public long getCooldownWakeup(SendableRequestItem token, ClientContext context) {
        MyKey key = (MyKey) token;
        return storage.segments[key.segmentNumber].getCooldownTime(key.blockNumber);
    }

    @Override
    public boolean preRegister(ClientContext context, boolean toNetwork) {
        if(!toNetwork) return false;
        // Notify clients of all the work we've done checking the datastore.
        if(parent.localRequestOnly()) {
            storage.finishedCheckingDatastoreOnLocalRequest(context);
            return true;
        } else {
            storage.setHasCheckedStore(context);
        }
        parent.toNetwork();
        parent.parent.notifyClients(context);
        return false;
    }

    @Override
    public short getPriorityClass() {
        return parent.getPriorityClass();
    }

    @Override
    /** Choose a random key to fetch. Must not modify anything that is persisted. */
    public SendableRequestItem chooseKey(KeysFetchingLocally keys, ClientContext context) {
        return storage.chooseRandomKey();
    }

    @Override
    public long countAllKeys(ClientContext context) {
        return storage.countUnfetchedKeys();
    }

    @Override
    public long countSendableKeys(ClientContext context) {
        return storage.countSendableKeys();
    }

    @Override
    public boolean isCancelled() {
        // FIXME locking on this is a bit different to the old code ... is it safe?
        return parent.hasFinished();
    }

    @Override
    public RequestClient getClient() {
        return parent.parent.getClient();
    }

    @Override
    public ClientRequester getClientRequest() {
        return parent.parent;
    }

    @Override
    public boolean isSSK() {
        return false;
    }

    /**
     * Schedule the fetch.
     * @param context
     * @param ignoreStore If true, don't check the datastore before re-registering the requests to
     * run. Should be true when rescheduling after a normal cooldown, false after recovering from
     * data corruption (the blocks may still be in the store), false otherwise.
     */
    public void schedule(ClientContext context, boolean ignoreStore) {
        ClientRequestScheduler sched = context.getChkFetchScheduler(realTimeFlag);
        BlockSet blocks = parent.blockFetchContext.blocks;
        sched.register(this, new SendableGet[] { this }, persistent, blocks, ignoreStore);
    }

    @Override
    public KeyListener makeKeyListener(ClientContext context, boolean onStartup) {
        return storage.keyListener;
    }

    public void cancel(ClientContext context) {
        unregister(context, parent.getPriorityClass());
    }

    /** Has preRegister() been called? */
    public boolean hasQueued() {
        return storage.hasCheckedStore();
    }

    @Override
    protected ClientGetState getClientGetState() {
        return parent;
    }
    
}
