package btools.server.request;

import btools.router.RoutingContext;
import btools.router.RoutingEngine;
import btools.server.ServiceContext;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * Custom profile uploads
 */
public class ProfileUploadHandler {
  // maximum number of characters (file size limit for custom profiles)
  private static final int MAX_LENGTH = 100000;

  // prefix for custom profile id to distinguish from default profiles
  public static final String CUSTOM_PREFIX = "custom_";
  public static final String SHARED_PREFIX = "shared_";

  private ServiceContext serviceContext;

  public ProfileUploadHandler(ServiceContext serviceContext) {
    this.serviceContext = serviceContext;
  }

  public void handlePostRequest(String profileId, BufferedReader br, BufferedWriter response) throws IOException {
    BufferedWriter fileWriter = null;

    try {
      String id;
      if (profileId != null) {
        // update existing file when id appended
        id = profileId.substring(ProfileUploadHandler.CUSTOM_PREFIX.length());
      } else {
        id = "" + System.currentTimeMillis();
      }
      File file = new File(getOrCreateCustomProfileDir(), id + ".brf");
      fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
      //StringWriter sw = new StringWriter(); bw = new BufferedWriter(sw);

      // only profile text as content
      readPostData(br, fileWriter, id);

      fileWriter.flush();
      //System.out.println("data: |" + sw.toString() + "|");

      Map<String, String> responseData = new HashMap<>();
      responseData.put("profileid", CUSTOM_PREFIX + id);

      validateProfile(id, responseData);

      response.write(toJSON(responseData));
    } finally {
      if (fileWriter != null) try {
          fileWriter.close();
        } catch (Exception e) {
        }
    }
  }

  private File getOrCreateCustomProfileDir() {
    // workaround: customProfileDir relative to profileDir, because RoutingEngine doesn't know custom profiles
    File customProfileDir = new File(serviceContext.profileDir, serviceContext.customProfileDir);
    if (!customProfileDir.exists()) {
      customProfileDir.mkdir();
    }
    return customProfileDir;
  }

  // reads HTTP POST content from input into output stream/writer
  private static void readPostData(BufferedReader ir, BufferedWriter bw, String id) throws IOException {
    // Content-Type: text/plain;charset=UTF-8

    int numChars = 0;

    // Content-Length header is in bytes (!= characters for UTF8),
    // but Reader reads characters, so don't know number of characters to read
    for (; ; ) {
      // read will block when false, occurs at end of stream rather than -1
      if (!ir.ready()) {
        try {
          Thread.sleep(1000);
        } catch (Exception e) {
        }
        if (!ir.ready()) break;
      }
      int c = ir.read();
      if (c == -1) break;
      bw.write(c);

      numChars++;
      if (numChars > MAX_LENGTH)
        throw new IOException("Maximum number of characters exceeded (" + MAX_LENGTH + ", " + id + ")");
    }
  }

  private String toJSON(Map<String, String> data) {
    boolean first = true;
    StringBuilder sb = new StringBuilder();
    sb.append("{");
    for (Map.Entry<String, String> entry : data.entrySet()) {
      sb.append(first ? "\n" : ",\n");
      sb.append("  \"");
      sb.append(entry.getKey());
      sb.append("\": \"");
      sb.append(entry.getValue());
      sb.append("\"");
      first = false;
    }
    sb.append("\n}\n");
    return sb.toString();
  }

  public void validateProfile(String id, Map<String, String> responseData) {
    // validate by initializing RoutingEngine, where parsing is done, and catching exceptions
    // see https://github.com/abrensch/brouter/issues/14
    try {
      RoutingContext rc = new RoutingContext();
      rc.localFunction = serviceContext.customProfileDir + "/" + id;
      new RoutingEngine(null, null, null, null, rc);
    } catch (Exception e) {
      String msg = e.getMessage();
      if (msg == null) {
        msg = "";
      } else if (msg.indexOf("does not contain expressions for context") >= 0) {
        // remove absolute path in this specific exception, useful for server, but don't disclose to client
        msg = msg.substring(msg.indexOf("does not contain expressions for context"));
      }
      responseData.put("error", "Profile error: " + msg);
    }
  }
}
