public abstract class Digest {

   // Ime hash funkcije 
   protected String name;

   // Velicina izlaza hash f-je u bajtovima 
   protected int hashSize;

   //Velicina blokova, u bajtovima,  na koje se deli ulaz hash f-je
   protected int blockSize;

   //Trenutni broj obradjenih bajtova
   protected long count;

   //Bafer u kome se cuva blok koji se trenutno obradjuje
   protected byte[] buffer;
   
   // Konstruktor
   protected Digest(String name, int hashSize, int blockSize) {
      super();

      this.name = name;
      this.hashSize = hashSize;
      this.blockSize = blockSize;
      this.buffer = new byte[blockSize];

      resetContext();
   }


   public String name() {
      return name;
   }

   public int hashSize() {
      return hashSize;
   }

   public int blockSize() {
      return blockSize;
   }
   
   //Dopunjava bafer uzimajuci neobradjene delove teksta sa ulaza
   public void update(byte b) {
	   	   
      int i = (int)(count % blockSize);
      count++;
      buffer[i] = b;
      if (i == (blockSize - 1)) {
         transform(buffer, 0);
      }
   }

   public void update(byte[] b) {

      update(b, 0, b.length);
   }

   public void update(byte[] b, int offset, int len) {
	   
      int n = (int)(count % blockSize);
      count += len;
      int partLen = blockSize - n;
      int i = 0;

      if (len >= partLen) {
         System.arraycopy(b, offset, buffer, n, partLen);
         transform(buffer, 0);
         for (i = partLen; i + blockSize - 1 < len; i+= blockSize) {
            transform(b, offset + i);
         }
         n = 0;
      }

      if (i < len) {
         System.arraycopy(b, offset + i, buffer, n, len - i);
      }
   }

   public byte[] digest(){
	   
      byte[] tail = padBuffer(); //Vrsi se dodavanje odredjenog sadrzaja na kraj ulaznog teksta u zavisnosti od vrste algoritma
      update(tail, 0, tail.length); // Obrada poslednjeg bloka ulaznog teksta
      byte[] result = getResult(); //Uzima rezultat koji se dobija kao izlaz algoritma
      
      reset(); // resetuje algoritam da bi se omogucila njegova upotreba nad novim ulazom
      
      return result;
   }

   public void reset() { // resetuje algoritam da bi se omogucila njegova upotreba nad novim ulazom
	  count = 0L;
      for (int i = 0; i < blockSize; ) {
         buffer[i++] = 0;
      }

      resetContext();
   }
   

   // Metode koje treba da se implementiraju u odgovarajucim podklasama

   public abstract Object clone();

  // public abstract boolean selfTest();

   //Vrsi nadopunjavanje ulaznog teksta odgovarajucim nizom bajtova
   protected abstract byte[] padBuffer();

   //Generise rezultat na osnovu sadrzaja odgovarajucih promenljivih koje su karakteristicne za svaki algoritam
   protected abstract byte[] getResult();

   //Vrsi resetovanje konteksta algoritma tako da moze ponovo da se koristi nad novim ulazom
   protected abstract void resetContext();

   //Vrsi transformaciju jednog bloka ulaznog teksta 
   protected abstract void transform(byte[] in, int offset);
}
