package uk.gov.register.serialization;

import org.apache.commons.codec.digest.DigestUtils;
import uk.gov.register.core.HashingAlgorithm;
import uk.gov.register.core.Item;
import uk.gov.register.core.Register;
import uk.gov.register.util.HashValue;

import java.nio.charset.StandardCharsets;
import java.util.*;

public class RSFExecutor {

    private Map<String, RegisterCommandHandler> registeredHandlers;


    public RSFExecutor() {
        registeredHandlers = new HashMap<>();
    }

    public void register(RegisterCommandHandler registerCommandHandler) {
        registeredHandlers.put(registerCommandHandler.getCommandName(), registerCommandHandler);
    }

    public RSFResult execute(RegisterSerialisationFormat rsf, Register register) {
        // this instead of integer can be full command
        // then when it's referenced just empty the value
        // when checking for orphan there should not be any hash with command as those
        // should have been emptied
        // ?? memory consumption here ??

        // for now hash and line from file?
        // hash and rsf file line number
        Map<String, Integer> shaRefLine = new HashMap<>();
        Iterator<RegisterCommand> commands = rsf.getCommands();
        int rsfLine = 1;
        while (commands.hasNext()) {
            RegisterCommand command = commands.next();

            RSFResult validationResult = validate(command, register, rsfLine, shaRefLine);
            if (!validationResult.isSuccessful()) {
                return validationResult;
            }

            RSFResult executionResult = execute(command, register);
            if (!executionResult.isSuccessful()) {
                return executionResult;
            }

            rsfLine++;
        }

        return validateOrphanItems(shaRefLine);
    }

    private RSFResult execute(RegisterCommand command, Register register) {
        if (registeredHandlers.containsKey(command.getCommandName())) {
            RegisterCommandHandler registerCommandHandler = registeredHandlers.get(command.getCommandName());
            return registerCommandHandler.execute(command, register);
        } else {
            return RSFResult.createFailResult("Handler not registered for command: " + command.getCommandName());
        }
    }

    private RSFResult validate(RegisterCommand command, Register register, int rsfLine, Map<String, Integer> shaRefLine) {
        // this ugly method won't be needed when we have symlinks
        // and won't have to rely on hashes

        String commandName = command.getCommandName();
        if (commandName.equals("add-item")) {
            String hash = DigestUtils.sha256Hex(command.getCommandArguments().get(0).getBytes(StandardCharsets.UTF_8));
            shaRefLine.put(HashingAlgorithm.SHA256 + ":" + hash, rsfLine);
        } else if (commandName.equals("append-entry")) {
            String sha256 = command.getCommandArguments().get(1);
            if (shaRefLine.containsKey(sha256)) {
                shaRefLine.put(sha256, 0);
            } else {
                HashValue hashValue = HashValue.decode(HashingAlgorithm.SHA256, sha256);
                Optional<Item> item = register.getItemBySha256(hashValue);
                if (!item.isPresent()) {
                    return RSFResult.createFailResult("Orphan append entry (line:" + rsfLine + "): " + command.toString());
                }
            }
        }
        return RSFResult.createSuccessResult();
    }

    private RSFResult validateOrphanItems(Map<String, Integer> shaRefLine) {
        List<String> orphanItems = new ArrayList<>();
        shaRefLine.forEach((hash, rsfLine) -> {
            if (rsfLine > 0) {
                orphanItems.add("Orphan add item (line:" + rsfLine + "): " + hash);
            }
        });

        if (orphanItems.isEmpty()) {
            return RSFResult.createSuccessResult();
        } else {
            return RSFResult.createFailResult(String.join("; ", orphanItems));
        }
    }
}
@@ -749,286 +749,8 @@
 ) %7B%0A
-        // this instead of integer can be full command%0A        // then when it's referenced just empty the value%0A        // when checking for orphan there should not be any hash with command as those%0A        // should have been emptied%0A        // ?? memory consumption here ??%0A%0A
     
@@ -800,24 +800,29 @@
      // hash
+value
  and rsf fil
@@ -843,30 +843,33 @@
         Map%3C
-String
+HashValue
 , Integer%3E s
@@ -863,27 +863,28 @@
 e, Integer%3E 
+ha
 sh
-a
 RefLine = ne
@@ -1158,19 +1158,20 @@
 sfLine, 
-s
 ha
+sh
 RefLine)
@@ -1523,19 +1523,20 @@
 anItems(
-s
 ha
+sh
 RefLine)
@@ -2108,30 +2108,33 @@
 sfLine, Map%3C
-String
+HashValue
 , Integer%3E s
@@ -2124,35 +2124,36 @@
 Value, Integer%3E 
+ha
 sh
-a
 RefLine) %7B%0A     
@@ -2483,35 +2483,36 @@
 ));%0A            
+ha
 sh
-a
 RefLine.put(Hash
@@ -2507,16 +2507,30 @@
 ine.put(
+new HashValue(
 HashingA
@@ -2548,21 +2548,15 @@
 A256
- + %22:%22 +
+,
  hash
+)
 , rs
@@ -2639,17 +2639,22 @@
  String 
-s
+entryS
 ha256 = 
@@ -2691,16 +2691,106 @@
 get(1);%0A
+            HashValue hashValue = HashValue.decode(HashingAlgorithm.SHA256, entrySha256);%0A
         
@@ -2797,19 +2797,20 @@
     if (
-s
 ha
+sh
 RefLine.
@@ -2821,22 +2821,25 @@
 ainsKey(
-sha256
+hashValue
 )) %7B%0A   
@@ -2843,35 +2843,36 @@
                 
-s
 ha
+sh
 RefLine.put(sha2
@@ -2867,22 +2867,25 @@
 ine.put(
-sha256
+hashValue
 , 0);%0A  
@@ -2907,97 +2907,8 @@
 e %7B%0A
-                HashValue hashValue = HashValue.decode(HashingAlgorithm.SHA256, sha256);%0A
     
@@ -3286,22 +3286,25 @@
 ems(Map%3C
-String
+HashValue
 , Intege
@@ -3306,19 +3306,20 @@
 nteger%3E 
+ha
 sh
-a
 RefLine)
@@ -3383,19 +3383,20 @@
         
+ha
 sh
-a
 RefLine.
@@ -3536,16 +3536,25 @@
 %22 + hash
+.encode()
 );%0A     

Change sha256 string to hash HashValue in reference line for RSFExecutor