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());
                    events.add(new CreateEvent(this.rootDir.relativize(file.toPath()), file.getName(), Hash.hash(Config.DEFAULT.getHashingAlgorithm(), file), timestamp));
                } catch (IOException e) {
                    logger.error("Could not hash contents of file " + file.toPath().toString());
                }
            } else if (file.isDirectory()) {
                // create createEvent for subdir
                events.add(new CreateEvent(this.rootDir.relativize(file.toPath()), file.getName(), null, timestamp));
                events.addAll(this.createCreateEventForChildren(file, timestamp));
            }
        }

        return events;
    }
}

@@ -3342,32 +3342,155 @@
 ry.toString());%0A
+                    // add additional n milliseconds such that the child contents are processed later than the parent ones%0A
                 
@@ -3632,32 +3632,110 @@
 file), timestamp
+ + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())
 ));%0A            
@@ -3969,24 +3969,143 @@
  for subdir%0A
+                // add additional n milliseconds such that the child contents are processed later than the parent ones%0A
             
@@ -4198,32 +4198,110 @@
  null, timestamp
+ + this.getAdditionalMilliseconds(parentDirectory.toString(), file.toString())
 ));%0A            
@@ -4424,11 +4424,857 @@
 s;%0A    %7D
+%0A%0A    /**%0A     * Returns the number of additional milliseconds to add for a child path relative to the event path%0A     * so that while traversing directories, their child entries are processed later than the parent.%0A     * All elements on the same level get the same number of additional milliseconds%0A     *%0A     * @param eventPath The path of the event%0A     * @param childPath The path to the child%0A     *%0A     * @return The number of additional milliseconds to add%0A     */%0A    protected int getAdditionalMilliseconds(String eventPath, String childPath) %7B%0A        int nrOfSlashes = eventPath.length() - eventPath.replace(%22/%22, %22%22).length();%0A        int nrOfSlashesInChild = childPath.length() - childPath.replace(%22/%22, %22%22).length();%0A%0A        // we add for each level one millisecond%0A        return Math.abs(nrOfSlashesInChild - nrOfSlashes);%0A    %7D
 %0A%7D%0A

Add aditional milliseconds to timestamp of children of directories