import 'dart:mirrors' as dart;

import 'package:angel_container/angel_container.dart';
import 'package:angel_container/src/reflector.dart';

/// A [Reflector] implementation that forwards to `dart:mirrors`.
///
/// Useful on the server, where reflection is supported.
class MirrorsReflector implements Reflector {
  const MirrorsReflector();

  @override
  String getName(Symbol symbol) => dart.MirrorSystem.getName(symbol);

  @override
  ReflectedClass reflectClass(Type clazz) {
    var mirror = dart.reflectType(clazz);

    if (mirror is dart.ClassMirror) {
      return new _ReflectedClassMirror(mirror);
    } else {
      throw new ArgumentError('$clazz is not a class.');
    }
  }

  @override
  ReflectedFunction reflectFunction(Function function) {
    var closure = dart.reflect(function) as dart.ClosureMirror;
    return new _ReflectedMethodMirror(closure.function);
  }

  @override
  ReflectedType reflectType(Type type) {
    var mirror = dart.reflectType(type);

    if (!mirror.hasReflectedType) {
      return reflectType(dynamic);
    } else {
      if (mirror is dart.ClassMirror) {
        return new _ReflectedClassMirror(mirror);
      } else {
        return new _ReflectedTypeMirror(mirror);
      }
    }
  }

  @override
  ReflectedInstance reflectInstance(Object object) {
    return new _ReflectedInstanceMirror(object);
  }
}

class _ReflectedTypeParameter extends ReflectedTypeParameter {
  final dart.TypeVariableMirror mirror;

  _ReflectedTypeParameter(this.mirror)
      : super(dart.MirrorSystem.getName(mirror.simpleName));
}

class _ReflectedTypeMirror extends ReflectedType {
  final dart.TypeMirror mirror;

  _ReflectedTypeMirror(this.mirror)
      : super(
          dart.MirrorSystem.getName(mirror.simpleName),
          mirror.typeVariables
              .map((m) => new _ReflectedTypeParameter(m))
              .toList(),
          mirror.reflectedType,
        );

  @override
  bool isAssignableTo(ReflectedType other) {
    return other is _ReflectedTypeMirror && mirror.isAssignableTo(other.mirror);
  }

  @override
  T newInstance<T>(String constructorName, List positionalArguments,
      [Map<String, dynamic> namedArguments, List<Type> typeArguments]) {
    throw new ReflectionException(
        '$name is not a class, and therefore cannot be instantiated.');
  }
}

class _ReflectedClassMirror extends ReflectedClass {
  final dart.ClassMirror mirror;

  _ReflectedClassMirror(this.mirror)
      : super(
          dart.MirrorSystem.getName(mirror.simpleName),
          mirror.typeVariables
              .map((m) => new _ReflectedTypeParameter(m))
              .toList(),
          [],
          [],
          _declarationsOf(mirror),
          mirror.reflectedType,
        );

  static List<ReflectedFunction> _constructorsOf(dart.ClassMirror mirror) {
    var out = <ReflectedFunction>[];

    for (var key in mirror.declarations.keys) {
      var value = mirror.declarations[key];

      if (value is dart.MethodMirror && value.isConstructor) {
        out.add(new _ReflectedMethodMirror(value));
      }
    }

    return out;
  }

  static List<ReflectedDeclaration> _declarationsOf(dart.ClassMirror mirror) {
    var out = <ReflectedDeclaration>[];

    for (var key in mirror.declarations.keys) {
      var value = mirror.declarations[key];

      if (value is dart.MethodMirror && !value.isConstructor) {
        out.add(new _ReflectedDeclarationMirror(
            dart.MirrorSystem.getName(key), value));
      }
    }

    return out;
  }

  @override
  List<ReflectedInstance> get annotations =>
      mirror.metadata.map((m) => new _ReflectedInstanceMirror(m)).toList();

  @override
  List<ReflectedFunction> get constructors => _constructorsOf(mirror);

  @override
  bool isAssignableTo(ReflectedType other) {
    return other is _ReflectedTypeMirror && mirror.isAssignableTo(other.mirror);
  }

  @override
  T newInstance<T>(String constructorName, List positionalArguments,
      [Map<String, dynamic> namedArguments, List<Type> typeArguments]) {
    return mirror
        .newInstance(new Symbol(constructorName), positionalArguments)
        .reflectee as T;
  }
}

class _ReflectedDeclarationMirror extends ReflectedDeclaration {
  final String name;
  final dart.MethodMirror mirror;

  _ReflectedDeclarationMirror(this.name, this.mirror)
      : super(name, mirror.isStatic, null);

  @override
  bool get isStatic => mirror.isStatic;

  @override
  ReflectedFunction get function => new _ReflectedMethodMirror(mirror);
}

class _ReflectedInstanceMirror extends ReflectedInstance {
  final dart.InstanceMirror mirror;

  _ReflectedInstanceMirror(this.mirror)
      : super(new _ReflectedClassMirror(mirror.type),
            new _ReflectedClassMirror(mirror.type), mirror.reflectee);

  @override
  T invoke<T>(Invocation invocation) {
    return mirror.delegate(invocation) as T;
  }
}

class _ReflectedMethodMirror extends ReflectedFunction {
  final dart.MethodMirror mirror;

  _ReflectedMethodMirror(this.mirror)
      : super(
            dart.MirrorSystem.getName(mirror.simpleName),
            <ReflectedTypeParameter>[],
            mirror.metadata
                .map((mirror) => new _ReflectedInstanceMirror(mirror))
                .toList(),
            !mirror.returnType.hasReflectedType
                ? const MirrorsReflector().reflectType(dynamic)
                : const MirrorsReflector()
                    .reflectType(mirror.returnType.reflectedType),
            mirror.parameters.map(_reflectParameter).toList(),
            mirror.isGetter,
            mirror.isSetter);

  static ReflectedParameter _reflectParameter(dart.ParameterMirror mirror) {
    return new ReflectedParameter(
        dart.MirrorSystem.getName(mirror.simpleName),
        mirror.metadata
            .map((mirror) => new _ReflectedInstanceMirror(mirror))
            .toList(),
        const MirrorsReflector().reflectType(mirror.type.reflectedType),
        !mirror.isOptional,
        mirror.isNamed);
  }
}