package anastore.client;

import anastore.store.MissHandler;
import anastore.store.NoSuchDatum;
import anastore.net.AsyncMessage;
import anastore.net.ReceiveChannel;
import anastore.net.SendChannel;
import anastore.net.SyncMessage;
import anastore.proto.*;
import anastore.util.Bug;
import anastore.util.BlockData;
import anastore.util.Log;
import anastore.util.Pair;
import anastore.util.TimeRange;
import anastore.util.ProtocolInterruptedException;
import anastore.util.ProtocolViolationException;

import java.io.IOException;

/**
 * A miss handler that retrieves blocks from the block manager running
 * on a given channel.
 */
class BlockRetriever implements MissHandler<BlockData>
{
    private static final boolean LOG = false;
    private static final Log log = new Log(LOG);

    private final SendChannel _chan;

    public BlockRetriever(SendChannel chan)
    {
        _chan = chan;
    }

    public Pair<TimeRange, BlockData> get(long id, long timestamp)
        throws NoSuchDatum
    {
        if (LOG)
            log.println("Retrieving block " + id + "@" + timestamp);
        return requestBlock(id, new GetBlockReq(id, timestamp));
    }

    public Pair<TimeRange, BlockData> getLatest(long id)
        throws NoSuchDatum
    {
        if (LOG)
            log.println("Retrieving latest block " + id);
        return requestBlock(id, new GetLatestReq(id));
    }

    private Pair<TimeRange, BlockData> requestBlock(long id, SyncMessage req)
        throws NoSuchDatum
    {
        ReceiveChannel rec;
        AsyncMessage res;
        try {
            rec = _chan.send(req);
            res = rec.receiveAsync();
        } catch (IOException e) {
            throw new ProtocolViolationException("Network error", e);
        } catch (InterruptedException e) {
            throw new ProtocolInterruptedException("Receiving block data", e);
        }
        if (res instanceof BlockRep) {
            BlockRep res2 = (BlockRep)res;
            if (LOG)
                log.println("Retrieved block " + id + ": " + res);
            return new Pair<TimeRange, BlockData>
                (res2.range, new BlockData(res2.data));
        } else if (res instanceof NoSuchDatumRep) {
            if (req instanceof GetBlockReq) {
                long timestamp = ((GetBlockReq)req).timestamp;
                if (LOG)
                    log.println("Block " + id + "@" + timestamp + " does not exist");
                throw new NoSuchDatum(id, timestamp);
            } else if (req instanceof GetLatestReq) {
                if (LOG)
                    log.println("Block " + id + " does not exist at current time");
                throw new NoSuchDatum(id);
            } else
                throw new Bug("Unknown request type " + req);
        } else {
            throw new ProtocolViolationException(res);
        }
    }
}
