package org.rmatil.sync.event.aggregator.core.modifier;

import org.rmatil.sync.commons.hashing.Hash;
import org.rmatil.sync.event.aggregator.config.Config;
import org.rmatil.sync.event.aggregator.core.events.CreateEvent;
import org.rmatil.sync.event.aggregator.core.events.DeleteEvent;
import org.rmatil.sync.event.aggregator.core.events.IEvent;
import org.rmatil.sync.persistence.exceptions.InputOutputException;
import org.rmatil.sync.version.api.IObjectStore;
import org.rmatil.sync.version.core.model.PathObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class AddDirectoryContentModifier implements IModifier {

    final static Logger logger = LoggerFactory.getLogger(AddDirectoryContentModifier.class);

    protected IObjectStore objectStore;

    protected Path rootDir;

    public AddDirectoryContentModifier(Path rootDir, IObjectStore objectStore) {
        this.objectStore = objectStore;
        this.rootDir = rootDir;
    }

    @Override
    public List<IEvent> modify(List<IEvent> events) {
        Collections.sort(events);
        List<IEvent> modifiedEvents = new ArrayList<>();

        for (IEvent event : events) {
            if (event instanceof DeleteEvent) {
                modifiedEvents.add(event);
                try {
                    // create for each child of the path a delete event
                    List<PathObject> deletedChildren = this.objectStore.getObjectManager().getChildren(event.getPath().toString());
                    modifiedEvents.addAll(
                            deletedChildren
                                    .stream()
                                    .map(entry -> new DeleteEvent(Paths.get(entry.getAbsolutePath()), entry.getName(), null, event.getTimestamp()))
                                    .collect(Collectors.toList()));
                } catch (InputOutputException e) {
                    logger.error("Failed to get deleted objects from directory " + event.getPath().toString() + ". Message: " + e.getMessage());
                }
            } else if (event instanceof CreateEvent) {
                modifiedEvents.add(event);
                // create createEvent for each file contained in the dir
                if (this.rootDir.resolve(event.getPath()).toFile().isDirectory()) {
                    modifiedEvents.addAll(createCreateEventForChildren(this.rootDir.resolve(event.getPath()).toFile(), event.getTimestamp()));
                }
            } else {
                modifiedEvents.add(event);
            }
        }

        Collections.sort(modifiedEvents);
        return modifiedEvents;
    }

    protected List<IEvent> createCreateEventForChildren(File parentDirectory, long timestamp) {
        List<IEvent> events = new ArrayList<>();
        if (null == parentDirectory) {
            return events;
        }

        // traverse each file
        for (File file : parentDirectory.listFiles()) {
            if (file.isFile()) {
                try {
                    logger.trace("Create createEvent for subfile " + file.toPath().toString() + " in parentDir " + parentDirectory.toString());
                    // add additional n milliseconds such that the child contents are processed later than the parent ones
                    events.add(new CreateEvent(this.rootDir.relativize(file.toPath()), file.getName(), Hash.hash(Config.DEFAULT.getHashingAlgorithm(), file), timestamp + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())));
                } catch (IOException e) {
                    logger.error("Could not hash contents of file " + file.toPath().toString());
                }
            } else if (file.isDirectory()) {
                // create createEvent for subdir
                // add additional n milliseconds such that the child contents are processed later than the parent ones
                events.add(new CreateEvent(this.rootDir.relativize(file.toPath()), file.getName(), null, timestamp + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())));
                events.addAll(this.createCreateEventForChildren(file, timestamp + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())));
            }
        }

        return events;
    }

    /**
     * Returns the number of additional milliseconds to add for a child path relative to the event path
     * so that while traversing directories, their child entries are processed later than the parent.
     * All elements on the same level get the same number of additional milliseconds
     *
     * @param eventPath The path of the event
     * @param childPath The path to the child
     *
     * @return The number of additional milliseconds to add
     */
    protected int getAdditionalMilliseconds(String eventPath, String childPath) {
        int nrOfSlashes = eventPath.length() - eventPath.replace("/", "").length();
        int nrOfSlashesInChild = childPath.length() - childPath.replace("/", "").length();

        // we add for each level one millisecond
        return Math.abs(nrOfSlashesInChild - nrOfSlashes);
    }
}

@@ -1973,16 +1973,102 @@
 estamp()
+ - this.getAdditionalMilliseconds(event.getPath().toString(), entry.getAbsolutePath())
 ))%0A     
@@ -3241,45 +3241,8 @@
 ) %7B%0A
-            if (file.isFile()) %7B%0A    
     
@@ -3267,28 +3267,24 @@
             
-   
- 
 logger.trace
@@ -3387,36 +3387,32 @@
 ry.toString());%0A
-    
                 
@@ -3522,36 +3522,32 @@
                 
-    
 events.add(new C
@@ -3763,36 +3763,32 @@
 ));%0A            
- 
-   
 %7D catch (IOExcep
@@ -3789,36 +3789,32 @@
 OException e) %7B%0A
-    
                 
@@ -3894,37 +3894,34 @@
 ));%0A            
-   
- 
 %7D
+%0A
 %0A            %7D e
@@ -3913,31 +3913,24 @@
             
-%7D else 
 if (file.isD
@@ -3947,57 +3947,8 @@
 ) %7B%0A
-                // create createEvent for subdir%0A
     
@@ -4066,204 +4066,8 @@
 nes%0A
-                events.add(new CreateEvent(this.rootDir.relativize(file.toPath()), file.getName(), null, timestamp + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())));%0A
     

Add order for delete events