part of cool_ui;

typedef GetKeyboardHeight = double Function(BuildContext context);
typedef KeyboardBuilder = Widget Function(
    BuildContext context, KeyboardController controller, String param);

class CoolKeyboard {
  static JSONMethodCodec _codec = const JSONMethodCodec();
  static KeyboardConfig _currentKeyboard;
  static Map<CKTextInputType, KeyboardConfig> _keyboards = {};
  static BuildContext _context;
  static OverlayEntry _keyboardEntry;
  static KeyboardController _keyboardController;
  static GlobalKey<KeyboardPageState> _pageKey;
  static bool isInterceptor = false;

  static ValueNotifier<double> _keyboardHeightNotifier = ValueNotifier(null)
    ..addListener(updateKeyboardHeight);

  static String _keyboardParam;

  static init(BuildContext context) {
    _context = context;
    interceptorInput();
  }

  static interceptorInput() {
    if (isInterceptor) return;
    isInterceptor = true;
    defaultBinaryMessenger.setMockMessageHandler("flutter/textinput",
        (ByteData data) async {
      var methodCall = _codec.decodeMethodCall(data);
      switch (methodCall.method) {
        case 'TextInput.show':
          if (_currentKeyboard != null) {
            openKeyboard();
            return _codec.encodeSuccessEnvelope(null);
          } else {
            return await _sendPlatformMessage("flutter/textinput", data);
          }
          break;
        case 'TextInput.hide':
          if (_currentKeyboard != null) {
            hideKeyboard();
            return _codec.encodeSuccessEnvelope(null);
          } else {
            return await _sendPlatformMessage("flutter/textinput", data);
          }
          break;
        case 'TextInput.setEditingState':
          var editingState = TextEditingValue.fromJSON(methodCall.arguments);
          if (editingState != null && _keyboardController != null) {
            _keyboardController.value = editingState;
            return _codec.encodeSuccessEnvelope(null);
          }
          break;
        case 'TextInput.clearClient':
          hideKeyboard(animation: true);
          clearKeyboard();
          break;
        case 'TextInput.setClient':
          var setInputType = methodCall.arguments[1]['inputType'];
          InputClient client;
          _keyboards.forEach((inputType, keyboardConfig) {
            if (inputType.name == setInputType['name']) {
              client = InputClient.fromJSON(methodCall.arguments);

              _keyboardParam = (client.configuration.inputType as CKTextInputType).params;

              clearKeyboard();
              _currentKeyboard = keyboardConfig;
              _keyboardController = KeyboardController(client: client)
                ..addListener(() {
                  var callbackMethodCall = MethodCall(
                      "TextInputClient.updateEditingState", [
                    _keyboardController.client.connectionId,
                    _keyboardController.value.toJSON()
                  ]);
                  defaultBinaryMessenger.handlePlatformMessage(
                      "flutter/textinput",
                      _codec.encodeMethodCall(callbackMethodCall),
                      (data) {});
                });
              if (_pageKey != null) {
                _pageKey.currentState?.update();
              }
            }
          });

          if (client != null) {
            await _sendPlatformMessage("flutter/textinput",
                _codec.encodeMethodCall(MethodCall('TextInput.hide')));
            return _codec.encodeSuccessEnvelope(null);
          } else {
            hideKeyboard(animation: false);
            clearKeyboard();
          }
          break;
      }
      ByteData response = await _sendPlatformMessage("flutter/textinput", data);
      return response;
    });
  }

  static Future<ByteData> _sendPlatformMessage(
      String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();
    ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
      try {
        completer.complete(reply);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context:
              ErrorDescription('during a platform message response callback'),
        ));
      }
    });
    return completer.future;
  }

  static addKeyboard(CKTextInputType inputType, KeyboardConfig config) {
    _keyboards[inputType] = config;
  }

  static openKeyboard() {
    var keyboardHeight = _currentKeyboard.getHeight(_context);
    _keyboardHeightNotifier.value = keyboardHeight;
    if (_keyboardEntry != null) return;
    _pageKey = GlobalKey<KeyboardPageState>();
    // KeyboardMediaQueryState queryState = _context
    //         .ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>())
    //     as KeyboardMediaQueryState;
    // queryState.update();

    var tempKey = _pageKey;
    _keyboardEntry = OverlayEntry(builder: (ctx) {
      if (_currentKeyboard != null && _keyboardHeightNotifier.value != null) {
        return KeyboardPage(
            key: tempKey,
            builder: (ctx) {
              return _currentKeyboard?.builder(ctx, _keyboardController, _keyboardParam);
            },
            height: _keyboardHeightNotifier.value);
      } else {
        return Container();
      }
    });

    Overlay.of(_context).insert(_keyboardEntry);

    BackButtonInterceptor.add((_) {
      CoolKeyboard.sendPerformAction(TextInputAction.done);
      return true;
    }, zIndex: 1, name: 'CustomKeyboard');
  }

  static hideKeyboard({bool animation = true}) {
    BackButtonInterceptor.removeByName('CustomKeyboard');
    if (_keyboardEntry != null && _pageKey != null) {
      _keyboardHeightNotifier.value = null;
      // _pageKey.currentState.animationController
      //     .addStatusListener((AnimationStatus status) {
      //   if (status == AnimationStatus.dismissed ||
      //       status == AnimationStatus.completed) {
      //     if (_keyboardEntry != null) {
      //       _keyboardEntry.remove();
      //       _keyboardEntry = null;
      //     }
      //   }
      // });
      if (animation) {
        _pageKey.currentState.exitKeyboard();
        Future.delayed(Duration(milliseconds: 116)).then((_) {
          if (_keyboardEntry != null) {
            _keyboardEntry.remove();
            _keyboardEntry = null;
          }
        });
      } else {
        _keyboardEntry.remove();
        _keyboardEntry = null;
      }
    }
    _pageKey = null;
    try {
      // KeyboardMediaQueryState queryState = _context
      //     .ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>())
      // as KeyboardMediaQueryState;
      // queryState.update();
    } catch (_) {}
  }

  static clearKeyboard() {
    _currentKeyboard = null;
    if (_keyboardController != null) {
      _keyboardController.dispose();
      _keyboardController = null;
    }
  }

  static sendPerformAction(TextInputAction action) {
    var callbackMethodCall = MethodCall("TextInputClient.performAction",
        [_keyboardController.client.connectionId, action.toString()]);
    defaultBinaryMessenger.handlePlatformMessage("flutter/textinput",
        _codec.encodeMethodCall(callbackMethodCall), (data) {});
  }

  static updateKeyboardHeight() {
    if (_pageKey != null && _pageKey.currentState != null) {
      _pageKey.currentState.updateHeight(_keyboardHeightNotifier.value);
    }
  }
}

class KeyboardConfig {
  final KeyboardBuilder builder;
  final GetKeyboardHeight getHeight;
  const KeyboardConfig({this.builder, this.getHeight});
}

class InputClient {
  final int connectionId;
  final TextInputConfiguration configuration;
  const InputClient({this.connectionId, this.configuration});

  factory InputClient.fromJSON(List<dynamic> encoded) {
    return InputClient(
        connectionId: encoded[0],
        configuration: TextInputConfiguration(
            inputType: CKTextInputType.fromJSON(encoded[1]['inputType']),
            obscureText: encoded[1]['obscureText'],
            autocorrect: encoded[1]['autocorrect'],
            actionLabel: encoded[1]['actionLabel'],
            inputAction: _toTextInputAction(encoded[1]['inputAction']),
            textCapitalization:
                _toTextCapitalization(encoded[1]['textCapitalization']),
            keyboardAppearance:
                _toBrightness(encoded[1]['keyboardAppearance'])));
  }

  static TextInputAction _toTextInputAction(String action) {
    switch (action) {
      case 'TextInputAction.none':
        return TextInputAction.none;
      case 'TextInputAction.unspecified':
        return TextInputAction.unspecified;
      case 'TextInputAction.go':
        return TextInputAction.go;
      case 'TextInputAction.search':
        return TextInputAction.search;
      case 'TextInputAction.send':
        return TextInputAction.send;
      case 'TextInputAction.next':
        return TextInputAction.next;
      case 'TextInputAction.previuos':
        return TextInputAction.previous;
      case 'TextInputAction.continue_action':
        return TextInputAction.continueAction;
      case 'TextInputAction.join':
        return TextInputAction.join;
      case 'TextInputAction.route':
        return TextInputAction.route;
      case 'TextInputAction.emergencyCall':
        return TextInputAction.emergencyCall;
      case 'TextInputAction.done':
        return TextInputAction.done;
      case 'TextInputAction.newline':
        return TextInputAction.newline;
    }
    throw FlutterError('Unknown text input action: $action');
  }

  static TextCapitalization _toTextCapitalization(String capitalization) {
    switch (capitalization) {
      case 'TextCapitalization.none':
        return TextCapitalization.none;
      case 'TextCapitalization.characters':
        return TextCapitalization.characters;
      case 'TextCapitalization.sentences':
        return TextCapitalization.sentences;
      case 'TextCapitalization.words':
        return TextCapitalization.words;
    }

    throw FlutterError('Unknown text capitalization: $capitalization');
  }

  static Brightness _toBrightness(String brightness) {
    switch (brightness) {
      case 'Brightness.dark':
        return Brightness.dark;
      case 'Brightness.light':
        return Brightness.light;
    }

    throw FlutterError('Unknown Brightness: $brightness');
  }
}

class CKTextInputType extends TextInputType {
  final String name;
  final String params;

  const CKTextInputType({this.name, bool signed, bool decimal, this.params})
      : super.numberWithOptions(signed: signed, decimal: decimal);

  @override
  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'name': name,
      'signed': signed,
      'decimal': decimal,
      'params': params
    };
  }

  @override
  String toString() {
    return '$runtimeType('
        'name: $name, '
        'signed: $signed, '
        'decimal: $decimal)';
  }

  bool operator ==(Object target) {
    if (target is CKTextInputType) {
      if (this.name == target.toString()) {
        return true;
      }
    }
    return false;
  }

  @override
  int get hashCode => this.toString().hashCode;

  factory CKTextInputType.fromJSON(Map<String, dynamic> encoded) {
    return CKTextInputType(
        name: encoded['name'],
        signed: encoded['signed'],
        decimal: encoded['decimal'],
        params: encoded['params']);
  }
}

class KeyboardPage extends StatefulWidget {
  final WidgetBuilder builder;
  final double height;
  const KeyboardPage({this.builder, this.height, Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => KeyboardPageState();
}

class KeyboardPageState extends State<KeyboardPage> {
  Widget _lastBuildWidget;
  bool isClose = false;
  double _height = 0;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    
    WidgetsBinding.instance.addPostFrameCallback((_){
      _height = widget.height;
      setState(()=>{});
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedPositioned(
      child: IntrinsicHeight(child: Builder(
        builder: (ctx) {
          var result = widget.builder(ctx);
          if (result != null) {
            _lastBuildWidget = result;
          }
          return ConstrainedBox(
            constraints: BoxConstraints(
                minHeight: 0,
                minWidth: 0,
                maxHeight: _height,
                maxWidth: _ScreenUtil.getScreenW(context)),
            child: _lastBuildWidget,
          );
        },
      )),
      left: 0,
      width: _ScreenUtil.getScreenW(context),
      bottom: _height * (isClose ? -1 : 0),
      height: _height,
      duration: Duration(milliseconds: 100),
    );
  }

  @override
  void dispose() {
    // if (animationController.status == AnimationStatus.forward ||
    //     animationController.status == AnimationStatus.reverse) {
    //   animationController.notifyStatusListeners(AnimationStatus.dismissed);
    // }
    // animationController.dispose();
    super.dispose();
  }

  exitKeyboard() {
    isClose = true;
  }

  update() {
    WidgetsBinding.instance.addPostFrameCallback((_){
      setState(()=>{});
    });
  }

  updateHeight(double height) {
    WidgetsBinding.instance.addPostFrameCallback((_){
      this._height = height ?? 0;
      setState(()=>{});
    });
  }
}