/*
 * Jooby https://jooby.io
 * Apache License Version 2.0 https://jooby.io/LICENSE.txt
 * Copyright 2014 Edgar Espina
 */
package io.jooby.internal.openapi;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;

public class ASMType {
  private static class TypeName {
    String name;
    String prefix;
    String suffix;
    List<TypeName> arguments = new ArrayList<>();

    @Override
    public String toString() {
      StringBuilder buff = new StringBuilder();
      if (prefix != null) {
        buff.append(prefix);
      }
      buff.append(name.replace("/", "."));
      if (arguments.size() > 0) {
        String argstring =
            arguments.stream().map(TypeName::toString).collect(Collectors.joining(",", "<", ">"));
        buff.append(argstring);
      }
      if (suffix != null) {
        buff.append(suffix);
      }
      return buff.toString();
    }
  }

  public static String parse(String signature) {
    return parse(signature, type -> true);
  }

  public static String parse(String signature, Predicate<String> filter) {
    String primitive = primitive(signature);
    if (primitive != null) {
      return primitive;
    }
    SignatureReader reader = new SignatureReader(signature);
    LinkedList<TypeName> stack = new LinkedList<>();
    SignatureVisitor visitor =
        new SignatureVisitor(Opcodes.ASM9) {
          @Override
          public void visitClassType(String name) {
            if (filter.test(name)) {
              if (stack.isEmpty()) {
                TypeName type = new TypeName();
                type.name = name;
                stack.push(type);
              } else {
                TypeName type = stack.peek();
                if (type.name == null) {
                  type.name = name;
                } else {
                  TypeName arg = new TypeName();
                  arg.name = name;
                  type.arguments.add(arg);
                  stack.push(arg);
                }
              }
            }
          }

          @Override
          public void visitEnd() {
            if (stack.size() > 1) {
              stack.pop();
            }
          }

          @Override
          public void visitBaseType(char descriptor) {
            visitClassType(String.valueOf(descriptor));
            TypeName type = stack.peek();
            type.prefix = "[";
            type.suffix = null;
          }

          @Override
          public SignatureVisitor visitArrayType() {
            TypeName type = new TypeName();
            type.prefix = "[L";
            type.suffix = ";";
            stack.push(type);
            return this;
          }
        };
    reader.accept(visitor);
    TypeName type = stack.pop();
    return type.toString();
  }

  private static String primitive(String value) {
    switch (value) {
      case "Z":
        return "boolean";
      case "C":
        return "char";
      case "B":
        return "byte";
      case "S":
        return "short";
      case "I":
        return "int";
      case "J":
        return "long";
      case "F":
        return "float";
      case "D":
        return "double";
      case "V":
        return "void";
    }
    return null;
  }
}
