package com.timgroup.eventstore.merging;

import com.timgroup.eventstore.api.ResolvedEvent;

import javax.annotation.Nonnull;
import java.time.Duration;
import java.time.Instant;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;

public interface MergingStrategy<T extends Comparable<T>> {

    @Nonnull T toComparable(ResolvedEvent event);

    @Nonnull default Duration delay() { return Duration.ZERO; }

    @Nonnull default MergingStrategy<T> withDelay(Duration delay) {
        return new DelayedMergingStrategy<>(delay, this);
    }

    final class StreamIndexMergingStrategy implements MergingStrategy<Integer> {
        @Nonnull
        @Override
        public Integer toComparable(ResolvedEvent event) {
            return 0;
        }
    }

    final class EffectiveTimestampMergingStrategy implements MergingStrategy<Instant> {
        private static final Pattern EFFECTIVE_TIMESTAMP_PATTERN = Pattern.compile("\"effective_timestamp\"\\s*:\\s*\"([^\"]+)\"");

        @Nonnull
        @Override
        public Instant toComparable(ResolvedEvent event) {
            return effectiveTimestampFrom(event);
        }

        private static Instant effectiveTimestampFrom(ResolvedEvent event) {
            String metadata = new String(event.eventRecord().metadata(), UTF_8);
            Matcher matcher = EFFECTIVE_TIMESTAMP_PATTERN.matcher(metadata);
            if (matcher.find()) {
                return Instant.parse(matcher.group(1));
            }
            throw new IllegalStateException("no timestamp in metadata of " + event);
        }
    }

    final class DelayedMergingStrategy<T extends Comparable<T>> implements MergingStrategy<T> {
        private final Duration delay;
        private final MergingStrategy<T> delegate;

        private DelayedMergingStrategy(Duration delay, MergingStrategy<T> delegate) {
            this.delay = requireNonNull(delay);
            this.delegate = requireNonNull(delegate);
        }

        @Nonnull
        @Override
        public T toComparable(ResolvedEvent event) {
            return delegate.toComparable(event);
        }

        @Nonnull
        @Override
        public Duration delay() {
            return this.delay;
        }
    }
}

@@ -1082,24 +1082,316 @@
 %5C%22%5D+)%5C%22%22);%0A%0A
+        private final boolean fallBackToTimestamp;%0A%0A        public EffectiveTimestampMergingStrategy() %7B%0A            this(false);%0A        %7D%0A%0A        public EffectiveTimestampMergingStrategy(boolean fallBackToTimestamp) %7B%0A            this.fallBackToTimestamp = fallBackToTimestamp;%0A        %7D%0A%0A
         @Non
@@ -1387,32 +1387,32 @@
        @Nonnull%0A
-
         @Overrid
@@ -1549,23 +1549,16 @@
 private 
-static 
 Instant 
@@ -1684,16 +1684,59 @@
 UTF_8);%0A
+            if (!metadata.isEmpty()) %7B%0A    
         
@@ -1812,24 +1812,28 @@
             
+  
+  
 if (matcher.
@@ -1838,24 +1838,28 @@
 r.find()) %7B%0A
+    
             
@@ -1902,16 +1902,143 @@
 up(1));%0A
+                %7D%0A            %7D%0A            if (fallBackToTimestamp) %7B%0A                return event.eventRecord().timestamp();%0A
         

Allow EffectiveTimestampMergingStrategy to fall back to use event record timestamp if no effective timestamp is in the metadata