import 'dart:collection';

class Config {
  General? general;
  List<Proxy>? proxies;
  List<ProxyGroup>? proxyGroups;
  List<Rule>? rules;
  HashMap<String, List<String>>? hosts;

  @override
  String toString() {
    String result = "";

    if (general != null) {
      result += "$general\n";
    }

    if (proxies != null && proxies!.isNotEmpty) {
      result += "[Proxy]\n";
      result += proxies!.map((e) => e.toString()).join("\n");
      result += "\n\n";
    }

    if (proxyGroups != null && proxyGroups!.isNotEmpty) {
      result += "[Proxy Group]\n";
      result += proxyGroups!.map((e) => e.toString()).join("\n");
      result += "\n\n";
    }

    if (rules != null && rules!.isNotEmpty) {
      result += "[Rule]\n";
      result += rules!.map((e) => e.toString()).join("\n");
      result += "\n\n";
    }

    if (hosts != null && hosts!.isNotEmpty) {
      result += "[Host]\n";
      result += hosts!.entries
          .map((e) => "${e.key} = ${e.value.join(", ")}")
          .join("\n");
      result += "\n\n";
    }

    return result;
  }
}

class Rule {
  Rule({required this.typeField, this.filter, required this.target});

  String typeField;
  String? filter;
  String target;

  @override
  String toString() {
    String result = typeField;

    if (filter != null) {
      result += ', $filter';
    }

    result += ', $target';

    return result;
  }
}

class ProxyGroup {
  ProxyGroup(
      {required this.tag,
      required this.protocol,
      this.actors,
      this.healthCheck,
      this.checkInterval,
      this.failTimeout,
      this.failover,
      this.fallbackCache,
      this.cacheSize,
      this.cacheTimeout,
      this.lastResort,
      this.healthCheckTimeout,
      this.healthCheckDelay,
      this.healthCheckActive,
      this.delayBase,
      this.method});

  String tag;
  String protocol;
  List<String>? actors;

  /// failover
  bool? healthCheck;
  int? checkInterval;
  int? failTimeout;
  bool? failover;
  bool? fallbackCache;
  int? cacheSize;
  int? cacheTimeout;
  String? lastResort;
  int? healthCheckTimeout;
  int? healthCheckDelay;
  int? healthCheckActive;

  /// tryall
  int? delayBase;

  /// static
  String? method;

  @override
  String toString() {
    String result = tag;

    result += " = $protocol";

    if (actors != null) {
      result += ", ${actors!.join(", ")}";
    }

    if (healthCheck != null) {
      result += ", health-check = $healthCheck";
    }

    if (checkInterval != null) {
      result += ", check-interval = $checkInterval";
    }

    if (failTimeout != null) {
      result += ", fail-timeout = $failTimeout";
    }

    if (failover != null) {
      result += ", failover = $failover";
    }

    if (fallbackCache != null) {
      result += ", fallback-cache = $fallbackCache";
    }

    if (cacheSize != null) {
      result += ", cache-size = $cacheSize";
    }

    if (cacheTimeout != null) {
      result += ", cache-timeout = $cacheTimeout";
    }

    if (lastResort != null) {
      result += ", last-resort = $lastResort";
    }

    if (healthCheckTimeout != null) {
      result += ", health-check-timeout = $healthCheckTimeout";
    }

    if (healthCheckDelay != null) {
      result += ", health-check-delay = $healthCheckDelay";
    }

    if (healthCheckActive != null) {
      result += ", health-check-active = $healthCheckActive";
    }

    if (delayBase != null) {
      result += ", delay-base = $delayBase";
    }

    if (method != null) {
      result += ", method = $method";
    }

    return result;
  }
}

class Proxy {
  Proxy(
      {required this.tag,
      required this.protocol,
      this.interface,
      this.address,
      this.port,
      this.encryptMethod,
      this.password,
      this.ws,
      this.tls,
      this.tlsCert,
      this.wsPath,
      this.wsHost,
      this.sni,
      this.username,
      this.amux,
      this.amuxMax,
      this.amuxCon,
      this.quic});

  String tag;
  String protocol;
  String? interface;

  /// address
  String? address;
  int? port;

  /// shadowsocks
  String? encryptMethod;

  /// shadowsocks, trojan
  String? password;

  bool? ws;
  bool? tls;
  String? tlsCert;
  String? wsPath;
  String? wsHost;

  /// trojan
  String? sni;

  /// vmess
  String? username;

  bool? amux;
  int? amuxMax;
  int? amuxCon;

  bool? quic;

  @override
  String toString() {
    String result = tag;

    result += " = $protocol";

    switch (protocol) {
      case 'direct':
      case 'drop':
      case 'reject':
        return result;
    }

    if (address != null && port != null) {
      result += ", $address, $port";
    }

    if (encryptMethod != null) {
      result += ", encrypt-method = $encryptMethod";
    }

    if (password != null) {
      result += ", password = $password";
    }

    if (ws != null) {
      result += ", ws = $ws";
    }

    if (tls != null) {
      result += ", tls = $tls";
    }

    if (tlsCert != null) {
      result += ", tls-cert = $tlsCert";
    }

    if (wsPath != null) {
      result += ", ws-path = $wsPath";
    }

    if (wsHost != null) {
      result += ", ws-host = $wsHost";
    }

    if (sni != null) {
      result += ", sni = $sni";
    }

    if (username != null) {
      result += ", username = $username";
    }

    if (amux != null) {
      result += ", amux = $amux";
    }

    if (amuxMax != null) {
      result += ", amux-max = $amuxMax";
    }

    if (amuxCon != null) {
      result += ", amux-con = $amuxCon";
    }

    if (quic != null) {
      result += ", quic = $quic";
    }

    if (interface != null) {
      result += ", interface = $interface";
    }

    return result;
  }
}

class General {
  General(
      {this.tun,
      this.tunFd,
      this.loglevel,
      this.logoutput,
      this.dnsServer,
      this.dnsInterface,
      this.alwaysRealIp,
      this.alwaysFakeIp,
      this.httpInterface,
      this.httpPort,
      this.socksInterface,
      this.socksPort,
      this.apiInterface,
      this.apiPort,
      this.routingDomainResolve});

  Tun? tun;
  dynamic tunFd;
  String? loglevel;
  String? logoutput;
  List<String>? dnsServer;
  String? dnsInterface;
  List<String>? alwaysRealIp;
  List<String>? alwaysFakeIp;
  String? httpInterface;
  int? httpPort;
  String? socksInterface;
  int? socksPort;
  String? apiInterface;
  int? apiPort;
  bool? routingDomainResolve;

  @override
  String toString() {
    String result = "\n[General]\n";

    if (tun != null) {
      result += "tun = $tun\n";
    }

    if (tunFd != null) {
      result += "tun-fd = $tunFd\n";
    }

    if (loglevel != null) {
      result += "loglevel = $loglevel\n";
    }

    if (logoutput != null) {
      result += "logoutput = $logoutput\n";
    }

    if (dnsServer != null) {
      result += "dns-server = ${dnsServer!.join(", ")}\n";
    }

    if (dnsInterface != null) {
      result += "dns-interface = $dnsInterface\n";
    }

    if (alwaysRealIp != null) {
      result += "always-real-ip = ${alwaysRealIp!.join(", ")}\n";
    }

    if (alwaysFakeIp != null) {
      result += "always-fake-ip = ${alwaysFakeIp!.join(", ")}\n";
    }

    if (routingDomainResolve != null) {
      result += "routing-domain-resolve = $routingDomainResolve\n";
    }

    if (httpInterface != null) {
      result += "http-interface = $httpInterface\n";
    }

    if (httpPort != null) {
      result += "http-port = $httpPort\n";
    }

    if (socksInterface != null) {
      result += "socks-interface = $socksInterface\n";
    }

    if (socksInterface != null) {
      result += "socks-port = $socksPort\n";
    }

    if (apiInterface != null) {
      result += "api-interface = $apiInterface\n";
    }

    if (apiPort != null) {
      result += "api-port = $apiPort\n";
    }

    return result;
  }
}

class Tun {
  Tun({this.name, this.address, this.netmask, this.gateway, this.mtu});

  String? name;
  String? address;
  String? netmask;
  String? gateway;
  int? mtu;

  @override
  String toString() {
    String result = "";

    if (name == 'auto') {
      result += "auto";

      return result;
    }

    if (name != null &&
        address != null &&
        netmask != null &&
        gateway != null &&
        mtu != null) {
      result += ", $name, $address, $netmask, $gateway, $mtu";
    }

    return result;
  }
}
