package com.github.kubatatami.judonetworking.callbacks;

import com.github.kubatatami.judonetworking.AsyncResult;
import com.github.kubatatami.judonetworking.exceptions.JudoException;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by Kuba on 28/03/15.
 */
public class MergeCallback<T> {

    int requests;

    int finishRequests = 0;

    boolean started = false;

    boolean canceled = false;

    Map<BaseCallback<?>, Integer> progressMap = new HashMap<>();

    Set<AsyncResult> asyncResultSet = new HashSet<>();

    Callback<T> finalCallback;

    T result;

    public MergeCallback(int requests) {
        this.requests = requests;
    }

    public MergeCallback(int requests, Callback<T> finalCallback) {
        this.requests = requests;
        this.finalCallback = finalCallback;
    }

    public final void addStart(AsyncResult asyncResult) {
        if (canceled) {
            return;
        }
        if (!started) {
            started = true;
            onMergeStart();
        }
        asyncResultSet.add(asyncResult);
    }

    public final void addProgress(BaseCallback<?> callback, int progress) {
        if (canceled) {
            return;
        }
        progressMap.put(callback, Math.min(100, progress));
        onMergeProgress(calculateProgress());
    }

    protected final int calculateProgress() {
        int progress = 0;
        for (int value : progressMap.values()) {
            progress += value;
        }
        return progress / requests;
    }

    public final void addSuccess() {
        if (canceled) {
            return;
        }
        finishRequests++;
        if (finishRequests == requests) {
            onMergeSuccess();
            onMergeFinish();
        }

    }

    public final void addError(JudoException e) {
        if (canceled) {
            return;
        }
        onMergeError(e);
        onMergeFinish();
        cancelAll();
        canceled = true;
    }

    private void cancelAll() {
        for (AsyncResult asyncResult : asyncResultSet) {
            asyncResult.cancel();
        }
    }

    public void setResult(T result) {
        this.result = result;
    }

    protected void onMergeStart() {
        if (finalCallback != null) {
            finalCallback.onStart(null, new MergeAsyncResult());
        }
    }

    protected void onMergeProgress(int progress) {
        if (finalCallback != null) {
            finalCallback.onProgress(progress);
        }
    }

    protected void onMergeSuccess() {
        if (finalCallback != null) {
            finalCallback.onSuccess(result);
        }
    }

    protected void onMergeError(JudoException e) {
        if (finalCallback != null) {
            finalCallback.onError(e);
        }
    }

    protected void onMergeFinish() {
        if (finalCallback != null) {
            finalCallback.onFinish();
        }
    }

    protected class MergeAsyncResult implements AsyncResult {

        @Override
        public boolean isDone() {
            for (AsyncResult asyncResult : asyncResultSet) {
                if (!asyncResult.isDone()) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public boolean isCancelled() {
            return canceled;
        }

        @Override
        public boolean isRunning() {
            for (AsyncResult asyncResult : asyncResultSet) {
                if (asyncResult.isRunning()) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public long getStartTimeMillis() {
            long startTimeMillis = System.currentTimeMillis();
            for (AsyncResult asyncResult : asyncResultSet) {
                startTimeMillis = Math.min(startTimeMillis, asyncResult.getStartTimeMillis());
            }
            return startTimeMillis;
        }

        @Override
        public void cancel() {
            cancelAll();
        }

        @Override
        public void await() throws InterruptedException {
            for (AsyncResult asyncResult : asyncResultSet) {
                asyncResult.await();
            }
        }

        @Override
        public Map<String, List<String>> getHeaders() {
            throw new UnsupportedOperationException("getHeaders of MergeCallback is not supported");
        }
    }
}

@@ -371,24 +371,32 @@
 k%3CT%3E %7B%0A%0A    
+private 
 int requests
@@ -398,24 +398,32 @@
 uests;%0A%0A    
+private 
 int finishRe
@@ -435,24 +435,32 @@
 s = 0;%0A%0A    
+private 
 boolean star
@@ -481,38 +481,53 @@
     
-boolean canceled = false;%0A%0A   
+private JudoException exception;%0A%0A    private
  Map
@@ -589,16 +589,24 @@
 );%0A%0A    
+private 
 Set%3CAsyn
@@ -653,16 +653,24 @@
 );%0A%0A    
+private 
 Callback
@@ -693,16 +693,24 @@
 k;%0A%0A    
+private 
 T result
@@ -1009,62 +1009,8 @@
 ) %7B%0A
-        if (canceled) %7B%0A            return;%0A        %7D%0A
     
@@ -1223,62 +1223,8 @@
 ) %7B%0A
-        if (canceled) %7B%0A            return;%0A        %7D%0A
     
@@ -1586,53 +1586,52 @@
     
-if (canceled) %7B%0A   
+finish();%0A
     
+%7D%0A%0A
     
- return;%0A        %7D
+private void finish() %7B
 %0A   
@@ -1711,25 +1711,141 @@
     
-onMergeSuccess();
+if(exception == null) %7B%0A                onMergeSuccess();%0A            %7D else %7B%0A                onMergeError(exception);%0A            %7D
 %0A   
@@ -1876,25 +1876,24 @@
 ;%0A        %7D%0A
-%0A
     %7D%0A%0A    p
@@ -1949,288 +1949,45 @@
     
-if (canceled) %7B%0A            return;%0A        %7D%0A        onMergeError(e);%0A        onMergeFinish();%0A        cancelAll();%0A        canceled = true;%0A    %7D%0A%0A    private void cancelAll() %7B%0A        for (AsyncResult asyncResult : asyncResultSet) %7B%0A            asyncResult.cancel();%0A        %7D
+this.exception = e;%0A        finish();
 %0A   
@@ -2841,24 +2841,59 @@
 ncResult %7B%0A%0A
+        boolean canceled = false;%0A%0A
         @Ove
@@ -3920,32 +3920,220 @@
 l();%0A        %7D%0A%0A
+        private void cancelAll() %7B%0A            canceled = true;%0A            for (AsyncResult asyncResult : asyncResultSet) %7B%0A                asyncResult.cancel();%0A            %7D%0A        %7D%0A%0A
         @Overrid

change merge callback onError flow