package com.prt2121.marbledcat.internal;

import android.os.Looper;
import android.util.Log;
import java.util.concurrent.ConcurrentSkipListSet;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * Created by pt2121 on 11/1/15.
 */
@Aspect public class MarbledCat {
  private static volatile boolean enabled = true;

  private final ConcurrentSkipListSet<Integer> lineSet = new ConcurrentSkipListSet<>();

  @Pointcut(value = "execution(* rx.functions.Func1.call(..))") public void func1Call() {
  }

  @Pointcut(value = "execution(* rx.functions.Func2.call(..))") public void func2Call() {
  }

  @Pointcut(value = "execution(* rx.functions.Action0.call(..))") public void action0Call() {
  }

  @Pointcut(value = "execution(* rx.functions.Action1.call(..))") public void action1Call() {
  }

  @Around("action0Call()") public Object aroundAction0Call(ProceedingJoinPoint joinPoint)
      throws Throwable {
    Object result = joinPoint.proceed();
    if (enabled) {
      StringBuilder builder = new StringBuilder();
      buildPrefix(joinPoint, builder);
      Signature signature = joinPoint.getSignature();
      Class<?> cls = signature.getDeclaringType();
      Log.v(asTag(cls), builder.append(" \u2601 ").toString());
    }
    return result;
  }

  @Around("action1Call()") public Object aroundAction1Call(ProceedingJoinPoint joinPoint)
      throws Throwable {
    return log(joinPoint);
  }

  @Around("func1Call()") public Object aroundFunc1Call(ProceedingJoinPoint joinPoint)
      throws Throwable {
    return log(joinPoint);
  }

  @Around("func2Call()") public Object aroundFunc2Call(ProceedingJoinPoint joinPoint)
      throws Throwable {
    return log(joinPoint);
  }

  private Object log(ProceedingJoinPoint joinPoint) throws Throwable {
    Object result = joinPoint.proceed();
    if (enabled) {
      StringBuilder builder = new StringBuilder();
      enter(joinPoint, builder);
      exit(joinPoint, result, builder);
    }
    return result;
  }

  private void enter(JoinPoint joinPoint, StringBuilder builder) {
    int line = buildPrefix(joinPoint, builder);
    int tab = lineSet.headSet(line).size();
    for (int i = 0; i < tab; i++) {
      builder.append("\t\u21e2  ");
    }
    CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
    Object[] parameterValues = joinPoint.getArgs();
    String[] parameterNames = codeSignature.getParameterNames();
    for (int i = 0; i < parameterValues.length; i++) {
      if (i > 0) {
        builder.append(", ");
      }
      builder.append(parameterNames[i]).append('=');
      builder.append(Strings.toString(parameterValues[i]));
    }
  }

  // add prefix (thread hash line) to StringBuilder and return line
  // i.e. RxComputationThreadPool-1	64f7292	54
  private int buildPrefix(JoinPoint joinPoint, StringBuilder builder) {
    if (Looper.myLooper() == Looper.getMainLooper()) {
      builder.append("MainThread               "); // 25 chars
    } else {
      builder.append(String.format("%25s", Thread.currentThread().getName()));
    }
    builder.append("\t");
    int hashCode = joinPoint.getTarget().hashCode();
    builder.append(String.format("%10s", Integer.toHexString(hashCode)));
    builder.append("\t");
    int line = joinPoint.getSourceLocation().getLine();
    builder.append(String.format("%4s", line));
    builder.append("\t");

    lineSet.add(line);
    return line;
  }

  private void exit(JoinPoint joinPoint, Object result, StringBuilder builder) {
    if (!enabled) return;

    Signature signature = joinPoint.getSignature();

    Class<?> cls = signature.getDeclaringType();
    boolean hasReturnType = signature instanceof MethodSignature
        && ((MethodSignature) signature).getReturnType() != void.class;

    if (hasReturnType) {
      builder.append(" \u21a6 ");
      builder.append(Strings.toString(result));
    }

    Log.v(asTag(cls), builder.toString());
  }

  private static String asTag(Class<?> cls) {
    if (cls.isAnonymousClass()) {
      return asTag(cls.getEnclosingClass());
    }
    return cls.getSimpleName();
  }

  public static void setEnabled(boolean enabled) {
    MarbledCat.enabled = enabled;
  }
}

@@ -745,17 +745,16 @@
 ons.Func
-1
 .call(..
@@ -778,104 +778,8 @@
 func
-1Call() %7B%0A  %7D%0A%0A  @Pointcut(value = %22execution(* rx.functions.Func2.call(..))%22) public void func2
 Call
@@ -891,107 +891,8 @@
  %7D%0A%0A
-  @Pointcut(value = %22execution(* rx.functions.Action1.call(..))%22) public void action1Call() %7B%0A  %7D%0A%0A
   @A
@@ -1119,27 +1119,27 @@
 ;%0A      
-buildPrefix
+//addThread
 (joinPoi
@@ -1355,155 +1355,8 @@
  %7D%0A%0A
-  @Around(%22action1Call()%22) public Object aroundAction1Call(ProceedingJoinPoint joinPoint)%0A      throws Throwable %7B%0A    return log(joinPoint);%0A  %7D%0A%0A
   @A
@@ -1366,17 +1366,16 @@
 nd(%22func
-1
 Call()%22)
@@ -1403,152 +1403,8 @@
 Func
-1Call(ProceedingJoinPoint joinPoint)%0A      throws Throwable %7B%0A    return log(joinPoint);%0A  %7D%0A%0A  @Around(%22func2Call()%22) public Object aroundFunc2
 Call
@@ -1859,27 +1859,25 @@
  line = 
-buildPrefix
+addThread
 (joinPoi
@@ -2002,10 +2002,10 @@
 %5Cu21
-e2
+E3
   %22)
@@ -2574,19 +2574,17 @@
 int 
-buildPrefix
+addThread
 (Joi
@@ -2880,283 +2880,55 @@
 int 
-hashCode = joinPoint.getTarget().hashCode();%0A    builder.append(String.format(%22%2510s%22, Integer.toHexString(hashCode)));%0A    builder.append(%22%5Ct%22);%0A    int line = joinPoint.getSourceLocation().getLine();%0A    builder.append(String.format(%22%254s%22, line));%0A    builder.append(%22%5Ct%22);%0A
+line = joinPoint.getSourceLocation().getLine();
 %0A   

Update log format