package ssw.mj.test.support;

import ssw.mj.codegen.Decoder;
import ssw.mj.impl.Parser;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;

public class ByteCodeTestSupport {
  /**
   * This flag is used by the lecturers to generate the reference solutions for
   * the bytecodes generated during code generation.
   * Students should not change this flag, it should stay false for the whole course.
   */
  public static final boolean GENERATE_REFERENCE_BYTE_CODE = false;

  // For each test, 0 to n correct byte codes can be added to bytecodes.txt
  // If one of these codes is generated by the Parser, the test does not fail.
  // This way, we can provide multiple correct solutions for the same test case.
  //
  // The keys of this map are in the format "TestClass.TestMethodName()"
  public static final HashMap<String, List<String>> BYTE_CODES = new HashMap<>();

  static {
    File bytecodesFile = getBytecodesFile();
    if (bytecodesFile.exists()) {
      String[] lineArr;
      try (Stream<String> lines = Files.lines(bytecodesFile.toPath())) {
        lineArr = lines.toArray(String[]::new);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      String currentlyReadClassAndMethod = null;
      StringBuilder currentReadBytecode = null;
      for (String line : lineArr) {
        if (line.isBlank()) {
          continue;
        }
        if (line.startsWith("#")) {
          if (currentlyReadClassAndMethod != null) {
            if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) {
              BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>());
            }
            BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString());
          }
          currentlyReadClassAndMethod = line.substring(1);
          currentReadBytecode = new StringBuilder();
        } else {
          currentReadBytecode.append(line).append("\n");
        }
      }
      if (currentlyReadClassAndMethod != null) {
        if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) {
          BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>());
        }
        BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString());
      }
    }
  }

  public static File getBytecodesFile() {
    String filename = "bytecodes.txt";
    ClassLoader classLoader = BaseCompilerTestCase.class.getClassLoader();
    URL resource = classLoader.getResource(filename);
    if (resource == null) {
      throw new RuntimeException("resource %s not found".formatted(filename));
    }
    String urlAsStr = resource.getFile();
    // replaces %20 Urlencoding with " " (blank space), as e.g. Linux cannot handle url paths
    String path = URLDecoder.decode(urlAsStr, StandardCharsets.UTF_8);
    return new File(path);
  }


  public static void generateReferenceByteCode(String classAndMethod, Parser parser) {
    // Generate and store bytecode for correct test programs

    // Output is in the form:
    // #TestClass.TestMethodName()
    // ... output from Decoder.decode() ...
    File bytecodesFile = ByteCodeTestSupport.getBytecodesFile();
    try (BufferedWriter bw = Files.newBufferedWriter(bytecodesFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
      String bytecode = new Decoder().decode(parser.code);
      bw.write("#");
      bw.write(classAndMethod);
      bw.write("\n");
      bw.write(bytecode);
      bw.flush();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
}
