package ai.decompile.die.model;

import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

@Component
public class DieResultParser {
  private static final Pattern COMPILER_PATTERN =
      Pattern.compile("Compiler:\\s*(.+)", Pattern.CASE_INSENSITIVE);
  private static final Pattern OS_PATTERN =
      Pattern.compile("Operation system:\\s*(.+)", Pattern.CASE_INSENSITIVE);
  private static final Pattern ARCH_BITS_PATTERN = Pattern.compile("\\[([^,\\]]+),\\s*(\\d+)-bit");

  private static final Map<String, String> FILETYPE_ARCH_HINTS =
      Map.ofEntries(
          Map.entry("NE", "x86, 16-bit"),
          Map.entry("LE", "x86, 32-bit"),
          Map.entry("LX", "x86, 32-bit"),
          Map.entry("PE", "x86, 32-bit"),
          Map.entry("PE32", "x86, 32-bit"),
          Map.entry("PE64", "x86-64, 64-bit"),
          Map.entry("PE32+", "x86-64, 64-bit"),
          Map.entry("MS-DOS", "x86, 16-bit"));

  public ParsedInfo parse(DieResult result) {
    if (Objects.isNull(result) || Objects.isNull(result.detects()) || result.detects().isEmpty()) {
      return new ParsedInfo(null, null, null);
    }

    String fileType = null;
    String architecture = null;
    String compiler = null;

    for (var detect : result.detects()) {
      if (Objects.isNull(fileType) && StringUtils.isNotBlank(detect.filetype())) {
        fileType = detect.filetype();
      }

      if (Objects.nonNull(detect.values())) {
        for (var value : detect.values()) {
          var text = firstNonBlank(value.string(), value.info(), value.name());

          if (Objects.isNull(compiler)) {
            compiler = tryExtractCompiler(value, text);
          }

          if (Objects.isNull(architecture)) {
            architecture = tryExtractArchitecture(text);
          }
        }
      }

      if (Objects.nonNull(fileType) && Objects.nonNull(architecture) && Objects.nonNull(compiler)) {
        break;
      }
    }

    if (Objects.isNull(architecture) && Objects.nonNull(fileType)) {
      architecture = FILETYPE_ARCH_HINTS.get(fileType);
    }

    return new ParsedInfo(fileType, architecture, compiler);
  }

  private String tryExtractCompiler(DieResult.Value value, String text) {
    if (Objects.isNull(text)) {
      return null;
    }

    var matched = extractFirstMatch(text, COMPILER_PATTERN);
    if (Objects.nonNull(matched)) {
      return matched;
    }

    if ("compiler".equalsIgnoreCase(value.type()) && StringUtils.isNotBlank(value.name())) {
      var version = StringUtils.isNotBlank(value.info()) ? "[" + value.info() + "]" : "";
      return value.name() + version;
    }

    return null;
  }

  private String tryExtractArchitecture(String text) {
    if (Objects.isNull(text)) {
      return null;
    }

    var osInfo = extractFirstMatch(text, OS_PATTERN);
    if (Objects.isNull(osInfo)) {
      return null;
    }

    var matcher = ARCH_BITS_PATTERN.matcher(osInfo);
    if (matcher.find()) {
      return matcher.group(1).trim() + ", " + matcher.group(2) + "-bit";
    }

    return osInfo.trim();
  }

  private String extractFirstMatch(String text, Pattern pattern) {
    var matcher = pattern.matcher(text);
    if (matcher.find()) {
      return matcher.group(1).trim();
    }

    return null;
  }

  private String firstNonBlank(String... values) {
    for (var v : values) {
      if (StringUtils.isNotBlank(v)) {
        return v;
      }
    }

    return null;
  }

  public record ParsedInfo(String fileType, String architecture, String compiler) {}
}
