package ee.shy.storage;

import ee.shy.io.Json;
import ee.shy.io.Jsonable;
import ee.shy.map.UnkeyableSimpleMap;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Map-like abstract storage class for binary data.
 * Access data by hash.
 *
 * @see Hash
 * @see MapStorage
 * @see FileStorage
 */
public abstract class DataStorage implements UnkeyableSimpleMap<Hash, InputStream> {
    /**
     * Adds binary data from input stream to storage.
     * @param source input stream to get data from
     * @return hash of stored data
     */
    @Override
    public final Hash put(InputStream source) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            DigestInputStream dis = new DigestInputStream(source, md);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            IOUtils.copy(dis, baos);

            Hash hash = new Hash(md);
            put(hash, new ByteArrayInputStream(baos.toByteArray()));
            return hash;
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Adds object as JSON to storage.
     * @param object object to store
     * @return hash of stored data
     */
    public final Hash put(Jsonable object) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DigestOutputStream dos = new DigestOutputStream(baos, md);

            object.write(dos);

            Hash hash = new Hash(md);
            put(hash, new ByteArrayInputStream(baos.toByteArray()));
            return hash;
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Adds binary data from input stream with precomputed hash.
     * @param hash hash of data from input stream
     * @param source input stream to get data from
     * @throws IOException if there was a problem reading the input stream or writing to some output
     */
    protected abstract void put(Hash hash, InputStream source) throws IOException;

    /**
     * Gets binary data as input stream by hash.
     * Input data is checked against given hash to ensure correctness.
     * @param hash hash of data to get
     * @return input stream of data
     */
    @Override
    public final InputStream get(Hash hash) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            DigestInputStream dis = new DigestInputStream(getUnchecked(hash), md);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            IOUtils.copy(dis, baos);

            Hash hashComputed = new Hash(md);
            if (!hash.equals(hashComputed))
                throw new RuntimeException("stored file content does not match hash");

            return new ByteArrayInputStream(baos.toByteArray());
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets object from JSON by hash.
     * @param hash hash of object to get
     * @param classofT class of object to get
     * @param <T> type of obect to get
     * @return object from JSON
     * @throws IOException
     */
    public final <T> T get(Hash hash, Class<T> classofT) throws IOException {
        return Json.read(get(hash), classofT);
    }

    /**
     * Gets binary data as input stream by hash.
     * Input data is <b>not</b> checked against given hash to ensure correctness.
     * @param hash hash of data to get
     * @return input stream of data
     * @throws IOException if there was a problem reading from some input
     */
    protected abstract InputStream getUnchecked(Hash hash) throws IOException;
}

@@ -2833,32 +2833,33 @@
 tance(%22SHA-1%22);%0A
+%0A
             Dige
@@ -2846,32 +2846,37 @@
 );%0A%0A            
+try (
 DigestInputStrea
@@ -2934,32 +2934,37 @@
 d);%0A            
+     
 ByteArrayOutputS
@@ -2996,34 +2996,41 @@
 yOutputStream();
-%0A%0A
+) %7B%0A%0A    
             IOUt
@@ -3055,32 +3055,36 @@
 );%0A%0A            
+    
 Hash hashCompute
@@ -3113,16 +3113,20 @@
         
+  
+  
 if (!has
@@ -3149,16 +3149,20 @@
 puted))%0A
+    
         
@@ -3249,24 +3249,28 @@
             
+    
 return new B
@@ -3310,34 +3310,42 @@
 ray());%0A        
+  
-%7D%0A
+  %7D%0A    
         catch (N
@@ -3339,39 +3339,26 @@
      catch (
-NoSuchAlgorithm
+IO
 Exception e)
@@ -3352,32 +3352,36 @@
 OException e) %7B%0A
+    
             thro
@@ -3399,32 +3399,46 @@
 meException(e);%0A
+            %7D%0A
         %7D%0A      
@@ -3438,34 +3438,47 @@
 %0A        catch (
-IO
+NoSuchAlgorithm
 Exception e) %7B%0A 

Fix DigestInputStream not being closed in DataStorage