/*
 * Copyright 2010 Outerthought bvba
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.lilyproject.repository.impl;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.hadoop.hbase.util.Bytes;
import org.lilyproject.repository.api.*;
import org.lilyproject.util.Pair;

public class BlobStoreAccessRegistry {

    Map<String, BlobStoreAccess> registry = new HashMap<String, BlobStoreAccess>();
    private BlobStoreAccessFactory blobStoreAccessFactory;
    private final BlobManager blobManager;
    
    public BlobStoreAccessRegistry(BlobManager blobManager) {
        this.blobManager = blobManager;
    }
    
    public void register(BlobStoreAccess blobStoreAccess) {
        registry.put(blobStoreAccess.getId(), blobStoreAccess);
    }
    
    public void setBlobStoreAccessFactory(BlobStoreAccessFactory blobStoreAccessFactory) {
        this.blobStoreAccessFactory = blobStoreAccessFactory;
        for (BlobStoreAccess blobStoreAccess : blobStoreAccessFactory.getAll()) {
            register(blobStoreAccess);
        }
    }

    public OutputStream getOutputStream(Blob blob) throws BlobException {
        BlobStoreAccess blobStoreAccess = blobStoreAccessFactory.get(blob);
        return new BlobOutputStream(blobStoreAccess.getOutputStream(blob), blobStoreAccess.getId(), blob, blobManager, blobStoreAccess.incubate());
    }

    public BlobAccess getBlobAccess(Blob blob) throws BlobNotFoundException, BlobException {
        Pair<String, byte[]> decodedKey = decodeKey(blob);
        BlobStoreAccess blobStoreAccess = registry.get(decodedKey.getV1());
        return new BlobAccessImpl(blob, blobStoreAccess, decodedKey.getV2());
    }

    private Pair<String, byte[]> decodeKey(Blob blob) throws BlobNotFoundException, BlobException {
        if (blob.getValue() == null) {
            throw new BlobNotFoundException(blob, "Blob has no reference to a blob in the blobstore", null);
        }
        Pair<String, byte[]> decodedKey;
        try {
            decodedKey = decode(blob.getValue());
        } catch (Exception e) {
            throw new BlobException("Failed to decode the blobkey of the blob <" + blob + ">", e);
        }
        return decodedKey;
    }

    
    public void delete(Blob blob) throws BlobNotFoundException, BlobException {
        Pair<String, byte[]> decodedKey = decodeKey(blob);
        BlobStoreAccess blobStoreAccess = registry.get(decodedKey.getV1());
        blobStoreAccess.delete(decodedKey.getV2());
    }
    
    public void delete(byte[] blobKey) throws BlobException {
        Pair<String,byte[]> decodedKey = decode(blobKey);
        BlobStoreAccess blobStoreAccess = registry.get(decodedKey.getV1());
        blobStoreAccess.delete(decodedKey.getV2());
    }
    
    static private byte[] encode(String id, byte[] blobKey) {
        byte[] bytes = new byte[0];
        bytes = Bytes.add(bytes, blobKey);
        byte[] idBytes = Bytes.toBytes(id);
        bytes = Bytes.add(bytes, idBytes);
        bytes = Bytes.add(bytes, Bytes.toBytes(idBytes.length));
        return bytes;
    }
    
    static private Pair<String, byte[]>  decode(byte[] key) {
        int sizeofInt = Bytes.SIZEOF_INT;
        int idLength = Bytes.toInt(key, key.length - sizeofInt, sizeofInt);
        String id = Bytes.toString(key, key.length - sizeofInt - idLength, idLength);
        byte[] blobKey = Bytes.head(key, key.length - sizeofInt - idLength);
        return new Pair<String, byte[]>(id, blobKey);
    }
    
    private class BlobOutputStream extends FilterOutputStream {

        private final Blob blob;
        private final String blobStoreAccessId;
        private final BlobManager blobManager;
        private final boolean incubate;

        public BlobOutputStream(OutputStream outputStream, String blobStoreAccessId, Blob blob, BlobManager blobManager, boolean incubate) {
            super(outputStream);
            this.blobStoreAccessId = blobStoreAccessId;
            this.blob = blob;
            this.blobManager = blobManager;
            this.incubate = incubate;
        }

        @Override
        public void close() throws IOException {
            super.close();
            byte[] encodedBlobKey = encode(blobStoreAccessId, blob.getValue());
            if (incubate) {
                blobManager.incubateBlob(encodedBlobKey);
            }
            blob.setValue(encodedBlobKey);
        }
    }


}

@@ -4330,16 +4330,47 @@
 ncubate;
+%0A        private long size = 0;
 %0A%0A      
@@ -5025,32 +5025,563 @@
 ncodedBlobKey);%0A
+            blob.setSize(size);%0A        %7D%0A        %0A        @Override%0A        public void write(byte%5B%5D b) throws IOException %7B%0A            super.out.write(b);%0A            size = size + b.length;%0A        %7D%0A        %0A        @Override%0A        public void write(int b) throws IOException %7B%0A            super.out.write(b);%0A            size = size + 1;%0A        %7D%0A        %0A        @Override%0A        public void write(byte%5B%5D b, int off, int len) throws IOException %7B%0A            super.out.write(b, off, len);%0A            size = size + len;%0A
         %7D%0A    %7D%0A

Put blob size in Blob object upon closing outputstream (#236)