package com.bigstark.gunner.library;

import android.os.Message;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Gunner {

    private volatile static Gunner instance;

    static Gunner getInstance() {
        if (instance == null) {
            synchronized (Gunner.class) {
                if (instance == null) {
                    instance = new Gunner();
                }
            }
        }

        return instance;
    }

    public static void shoot(Object target) {
        Gunner instance = getInstance();
        Method[] methods = target.getClass().getDeclaredMethods();
        for (Method method : methods) {

            Bullet bullet = method.getAnnotation(Bullet.class);
            if (bullet == null) {
                continue;
            }

            method.setAccessible(true);
            int sequence = bullet.sequence();
            long delay = bullet.delay();

            instance.enqueue(method, sequence, delay);
        }

        instance.shootInternal(target);
    }


    public static void cancel(Object target) {
        getInstance().cancelInternal();
    }

    private ArrayList<BulletHandler> methodSet = new ArrayList<>();
    private ArrayList<BulletHandler> runnableSet = new ArrayList<>();

    private GunHandler gunHandler = new GunHandler();

    void enqueue(Method method, int sequence, long delay) {
        if (sequence < 0) {
            throw new IllegalArgumentException(String.format("[%s]'s sequence must be over 0. Please check the sequence", method.getName()));
        }

        if (delay < 0) {
            throw new IllegalArgumentException(String.format("[%s]'s delay must be over 0. Please check the delay", method.getName()));
        }

        BulletHandler handler = new BulletHandler(method, sequence, delay);
        methodSet.add(handler);
    }

    void enqueue(Runnable bullet, int sequence, long delay) {
        if (sequence < 0) {
            throw new IllegalArgumentException("Sequence must be over 0. Please check the sequence");
        }

        if (delay < 0) {
            throw new IllegalArgumentException("Delay must be over 0. Please check the delay");
        }

        BulletHandler handler = new BulletHandler(bullet, sequence, delay);
        runnableSet.add(handler);
    }


    void shootInternal(Object target) {
        Collections.sort(methodSet, bulletComparator);
        gunHandler.setTarget(target);

        long delay = 0;

        for (BulletHandler bulletHandler : methodSet) {
            delay += bulletHandler.getDelay();

            Message msg = gunHandler.obtainMessage(GunHandler.WHAT_METHOD);
            msg.obj = bulletHandler.getMethod();;
            gunHandler.sendMessageDelayed(msg, delay);
        }

        methodSet.clear();
    }

    void shootInternal() {
        Collections.sort(runnableSet, bulletComparator);
        long delay = 0;

        for (BulletHandler bulletHandler : runnableSet) {
            delay += bulletHandler.getDelay();

            Message msg = gunHandler.obtainMessage(GunHandler.WHAT_RUNNABLE);
            msg.obj = bulletHandler.getRunnable();
            gunHandler.sendMessageDelayed(msg, delay);
        }

        runnableSet.clear();
    }

    void cancelInternal() {
        gunHandler.removeMessages(GunHandler.WHAT_METHOD);
        gunHandler.removeMessages(GunHandler.WHAT_RUNNABLE);
    }

    public static class Revolver {

        public Revolver() {}

        public Revolver load(Runnable bullet) {
            return load(bullet, 0, 0);
        }

        public Revolver load(Runnable bullet, long delay) {
            return load(bullet, 0, delay);
        }

        public Revolver load(Runnable bullet, int sequence, long delay) {
            Gunner.getInstance().enqueue(bullet, sequence, delay);
            return this;
        }

        public void shoot() {
            Gunner.getInstance().shootInternal();
        }

        public void cancel() {
            Gunner.getInstance().cancelInternal();
        }

    }

    class BulletHandler {

        private Method method;
        private Runnable runnable;
        private int sequence;
        private long delay;

        public BulletHandler(Method method, int sequence, long delay) {
            this.method = method;
            this.sequence = sequence;
            this.delay = delay;
        }

        public BulletHandler(Runnable runnable, int sequence, long delay) {
            this.runnable = runnable;
            this.sequence = sequence;
            this.delay = delay;
        }

        public Method getMethod() {
            return method;
        }

        public Runnable getRunnable() {
            return runnable;
        }

        public int getSequence() {
            return sequence;
        }

        public long getDelay() {
            return delay;
        }
    }

    Comparator<BulletHandler> bulletComparator = new Comparator<BulletHandler>() {
        @Override
        public int compare(BulletHandler lhs, BulletHandler rhs) {
            int lSeq = lhs.getSequence();
            int rSeq = rhs.getSequence();
            return rSeq < lSeq ? 1 : rSeq == lSeq ? 0 : -1;
        }
    };


}

@@ -249,16 +249,17 @@
 tance;%0A%0A
+%0A
     stat
@@ -510,32 +510,33 @@
 nstance;%0A    %7D%0A%0A
+%0A
     public stati
@@ -1185,24 +1185,25 @@
 l();%0A    %7D%0A%0A
+%0A
     private 
@@ -1262,79 +1262,8 @@
 ();%0A
-    private ArrayList%3CBulletHandler%3E runnableSet = new ArrayList%3C%3E();%0A%0A
     
@@ -1309,24 +1309,25 @@
 Handler();%0A%0A
+%0A
     void enq
@@ -1846,460 +1846,8 @@
  %7D%0A%0A
-    void enqueue(Runnable bullet, int sequence, long delay) %7B%0A        if (sequence %3C 0) %7B%0A            throw new IllegalArgumentException(%22Sequence must be over 0. Please check the sequence%22);%0A        %7D%0A%0A        if (delay %3C 0) %7B%0A            throw new IllegalArgumentException(%22Delay must be over 0. Please check the delay%22);%0A        %7D%0A%0A        BulletHandler handler = new BulletHandler(bullet, sequence, delay);%0A        runnableSet.add(handler);%0A    %7D%0A%0A
 %0A   
@@ -2336,1253 +2336,102 @@
  %7D%0A%0A
-    void shootInternal() %7B%0A        Collections.sort(runnableSet, bulletComparator);%0A        long delay = 0;%0A%0A        for (BulletHandler bulletHandler : runnableSet) %7B%0A            delay += bulletHandler.getDelay();%0A%0A            Message msg = gunHandler.obtainMessage(GunHandler.WHAT_RUNNABLE);%0A            msg.obj = bulletHandler.getRunnable();%0A            gunHandler.sendMessageDelayed(msg, delay);%0A        %7D%0A%0A        runnableSet.clear();%0A    %7D%0A%0A    void cancelInternal() %7B%0A        gunHandler.removeMessages(GunHandler.WHAT_METHOD);%0A        gunHandler.removeMessages(GunHandler.WHAT_RUNNABLE);%0A    %7D%0A%0A    public static class Revolver %7B%0A%0A        public Revolver() %7B%7D%0A%0A        public Revolver load(Runnable bullet) %7B%0A            return load(bullet, 0, 0);%0A        %7D%0A%0A        public Revolver load(Runnable bullet, long delay) %7B%0A            return load(bullet, 0, delay);%0A        %7D%0A%0A        public Revolver load(Runnable bullet, int sequence, long delay) %7B%0A            Gunner.getInstance().enqueue(bullet, sequence, delay);%0A            return this;%0A        %7D%0A%0A        public void shoot() %7B%0A            Gunner.getInstance().shootInternal();%0A        %7D%0A%0A        public void cancel() %7B%0A            Gunner.getInstance().cancelInternal();%0A        %7D%0A%0A    %7D
+%0A    void cancelInternal() %7B%0A        gunHandler.removeMessages(GunHandler.WHAT_METHOD);%0A    %7D%0A
 %0A%0A  
@@ -2490,43 +2490,8 @@
 od;%0A
-        private Runnable runnable;%0A
     
@@ -2751,329 +2751,54 @@
 lic 
-BulletHandler(Runnable runnable, int sequence, long delay) %7B%0A            this.runnable = runnable;%0A            this.sequence = sequence;%0A            this.delay = delay;%0A        %7D%0A%0A        public Method getMethod() %7B%0A            return method;%0A        %7D%0A%0A        public Runnable getRunnable() %7B%0A            return runnable
+Method getMethod() %7B%0A            return method
 ;%0A  

remove revolver and runnable method because it is unused