part of angel_route.src.router;

class RouteGrammar {
  static final RegExp rgx = new RegExp(r'\((.+)\)');
  static final Parser<String> notSlash =
      match<String>(new RegExp(r'[^/]+')).value((r) => r.span.text);

  static final Parser<RegExp> regExp = match<RegExp>(new RegExp(r'\((.+)\)'))
      .value((r) => new RegExp(r.scanner.lastMatch[1]));

  static final Parser<String> parameterName =
      match<String>(new RegExp(r':([A-Za-z0-9_]+)'))
          .value((r) => r.span.text.substring(1));

  static final Parser<ParameterSegment> parameterSegment = chain([
    parameterName,
    match<bool>('?').value((r) => true).opt(),
    regExp.opt(),
  ]).map((r) {
    var s = new ParameterSegment(r.value[0] as String, r.value[2] as RegExp);
    return r.value[1] == true ? new OptionalSegment(s) : s;
  });

  static final Parser<ParsedParameterSegment> parsedParameterSegment = chain([
    match(new RegExp(r'(int|num|double)'),
            errorMessage: 'Expected "int","double", or "num".')
        .map((r) => r.span.text),
    parameterSegment,
  ]).map((r) {
    return new ParsedParameterSegment(
        r.value[0] as String, r.value[1] as ParameterSegment);
  });

  static final Parser<WildcardSegment> wildcardSegment =
      match<WildcardSegment>('*').value((r) => new WildcardSegment());

  static final Parser<ConstantSegment> constantSegment =
      notSlash.map<ConstantSegment>((r) => new ConstantSegment(r.value));

  static final Parser<RouteSegment> routeSegment = any<RouteSegment>([
    parsedParameterSegment,
    parameterSegment,
    wildcardSegment,
    constantSegment
  ]);

  static final Parser<RouteDefinition> routeDefinition = routeSegment
      .separatedBy(match('/'))
      .map<RouteDefinition>((r) => new RouteDefinition(r.value ?? []))
      .surroundedBy(match('/').star().opt());
}

class RouteDefinition {
  final List<RouteSegment> segments;

  RouteDefinition(this.segments);

  Parser<Map<String, dynamic>> compile() {
    Parser<Map<String, dynamic>> out;

    for (int i = 0; i < segments.length; i++) {
      var s = segments[i];
      bool isLast = i == segments.length - 1;
      if (out == null)
        out = s.compile(isLast);
      else
        out = s.compileNext(
            out.then(match('/')).index(0).cast<Map<String, dynamic>>(), isLast);
    }

    return out;
  }
}

abstract class RouteSegment {
  Parser<Map<String, dynamic>> compile(bool isLast);

  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast);
}

class ConstantSegment extends RouteSegment {
  final String text;

  ConstantSegment(this.text);

  @override
  String toString() {
    return 'Constant: $text';
  }

  @override
  Parser<Map<String, dynamic>> compile(bool isLast) {
    return match<Map<String, dynamic>>(text).value((r) => <String, dynamic>{});
  }

  @override
  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast) {
    return p.then(compile(isLast)).index(0).cast<Map<String, dynamic>>();
  }
}

class WildcardSegment extends RouteSegment {
  @override
  String toString() {
    return 'Wildcard segment';
  }

  Parser<Map<String, dynamic>> _compile(bool isLast) {
    if (isLast) return match(new RegExp(r'.*'));
    return match(new RegExp(r'[^/]*'));
  }

  @override
  Parser<Map<String, dynamic>> compile(bool isLast) {
    return _compile(isLast).map((r) => {});
  }

  @override
  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast) {
    return p.then(_compile(isLast)).index(0).cast<Map<String, dynamic>>();
  }
}

class OptionalSegment extends ParameterSegment {
  final ParameterSegment parameter;

  OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);

  @override
  String toString() {
    return 'Optional: $parameter';
  }

  @override
  Parser<Map<String, dynamic>> compile(bool isLast) {
    return super.compile(isLast).opt();
  }

  @override
  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast) {
    return p.then(_compile().opt()).map((r) {
      if (r.value[1] == null) return r.value[0] as Map<String, dynamic>;
      return (r.value[0] as Map<String, dynamic>)
        ..addAll({name: Uri.decodeComponent(r.value[1] as String)});
    });
  }
}

class ParameterSegment extends RouteSegment {
  final String name;
  final RegExp regExp;

  ParameterSegment(this.name, this.regExp);

  @override
  String toString() {
    if (regExp != null) return 'Param: $name (${regExp.pattern})';
    return 'Param: $name';
  }

  Parser<String> _compile() {
    return regExp != null
        ? match<String>(regExp).value((r) => r.span.text)
        : RouteGrammar.notSlash;
  }

  @override
  Parser<Map<String, dynamic>> compile(bool isLast) {
    return _compile().map<Map<String, dynamic>>(
        (r) => {name: Uri.decodeComponent(r.span.text)});
  }

  @override
  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast) {
    return p.then(_compile()).map((r) {
      return (r.value[0] as Map<String, dynamic>)
        ..addAll({name: Uri.decodeComponent(r.value[1] as String)});
    });
  }
}

class ParsedParameterSegment extends RouteSegment {
  final String type;
  final ParameterSegment parameter;

  ParsedParameterSegment(this.type, this.parameter);

  num getValue(String s) {
    switch (type) {
      case 'int':
        return int.parse(s);
      case 'double':
        return double.parse(s);
      default:
        return num.parse(s);
    }
  }

  @override
  Parser<Map<String, dynamic>> compile(bool isLast) {
    return parameter._compile().map(
        (r) => {parameter.name: getValue(Uri.decodeComponent(r.span.text))});
  }

  @override
  Parser<Map<String, dynamic>> compileNext(
      Parser<Map<String, dynamic>> p, bool isLast) {
    return p.then(parameter._compile()).map((r) {
      return (r.value[0] as Map<String, dynamic>)
        ..addAll({
          parameter.name: getValue(Uri.decodeComponent(r.value[1] as String))
        });
    });
  }
}