package com.onthegomap.flatmap.util;

import java.util.Map;
import java.util.regex.Pattern;

/**
 * Utilities to parse values from strings.
 */
public class Parse {

  private Parse() {
  }

  private static final Pattern INT_SUBSTRING_PATTERN = Pattern.compile("^(-?\\d+)(\\D|$)");
  private static final Pattern TO_ROUND_INT_SUBSTRING_PATTERN = Pattern.compile("^(-?[\\d.]+)(\\D|$)");

  /** Returns {@code tag} as a long or null if invalid. */
  public static Long parseLongOrNull(Object tag) {
    try {
      return tag == null ? null : tag instanceof Number number ? number.longValue() : Long.parseLong(tag.toString());
    } catch (NumberFormatException e) {
      return null;
    }
  }

  /** Returns {@code tag} as a long or 0 if invalid. */
  public static long parseLong(Object tag) {
    try {
      return tag == null ? 0 : tag instanceof Number number ? number.longValue() : Long.parseLong(tag.toString());
    } catch (NumberFormatException e) {
      return 0;
    }
  }

  /** Returns {@code tag} as an integer or null if invalid, ignoring any non-numeric suffix. */
  public static Integer parseIntSubstring(String tag) {
    if (tag == null) {
      return null;
    }
    try {
      var matcher = INT_SUBSTRING_PATTERN.matcher(tag);
      return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
    } catch (NumberFormatException e) {
      return null;
    }
  }

  /** Returns {@code tag} as a number rounded to the nearest integer or null if invalid. */
  public static Integer parseRoundInt(Object tag) {
    if (tag == null) {
      return null;
    }
    try {
      var matcher = TO_ROUND_INT_SUBSTRING_PATTERN.matcher(tag.toString());
      return matcher.find() ? Math.round(Float.parseFloat(matcher.group(1))) : null;
    } catch (NumberFormatException e) {
      return null;
    }
  }

  /** Returns {@code tag} as an integer or null if invalid. */
  public static Integer parseIntOrNull(Object tag) {
    if (tag instanceof Number num) {
      return num.intValue();
    }
    if (!(tag instanceof String)) {
      return null;
    }
    try {
      return Integer.parseInt(tag.toString());
    } catch (NumberFormatException e) {
      return null;
    }
  }

  /** Returns {@code tag} parsed as a boolean. */
  public static boolean bool(Object tag) {
    return Imposm3Parsers.bool(tag);
  }

  /** Returns {@code tag} parsed as an integer where 1 is true, 0 is false. */
  public static int boolInt(Object tag) {
    return Imposm3Parsers.boolInt(tag);
  }

  /** Returns {@code tag} parsed as an integer where 1 is true, 0 is false. */
  public static int direction(Object input) {
    return Imposm3Parsers.direction(input);
  }

  /**
   * Returns {@code tag} for an OSM road based on the tags that are present. Bridges are above roads appear above
   * tunnels and major roads appear above minor.
   */
  public static int wayzorder(Map<String, Object> tags) {
    return Imposm3Parsers.wayzorder(tags);
  }

  /** Returns {@code tag} as a double or null if invalid. */
  public static Double parseDoubleOrNull(Object value) {
    if (value instanceof Number num) {
      return num.doubleValue();
    }
    if (value == null) {
      return null;
    }
    try {
      return Double.parseDouble(value.toString());
    } catch (NumberFormatException e) {
      return null;
    }
  }
}
