import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:nut_player_platform_interface/nut_player_platform_interface.dart';

/// An Android implementation of [NutPlayerPlatform]
class NutPlayerAndroidPlatform extends NutPlayerPlatform {

  final _pluginChannel = MethodChannel('tech.nut/plugin');
  final _logStreamController = StreamController<String>();
  late Stream<String> _logStream;

  EventChannel _eventChannelFor(PlayerId playerId) {
    return EventChannel('tech.nut/videoPlayer/videoEvents/$playerId');
  }

  Future<dynamic> _pluginMethodHandler(MethodCall call) async {
    switch (call.method) {
      case 'pluginLogMessage':
        final message = call.arguments['message'];
        if (message != null) {
          _logStreamController.add(message);
        }
        break;
      default:
        break;
    }
  }

  /// Registers this class as the default instance of [PathProviderPlatform].
  static void registerWith() {
    NutPlayerPlatform.instance = NutPlayerAndroidPlatform();
  }

  @override
  Future<void> init() {
    _logStream = _logStreamController.stream.asBroadcastStream();
    _pluginChannel.setMethodCallHandler(_pluginMethodHandler);
    return _pluginChannel.invokeMethod('pluginInitialize');
  }

  @override
  Future<void> dispose(PlayerId playerId) {
    return _pluginChannel.invokeMethod('pluginDispose', [playerId]);
  }

  @override
  Future<PlayerId> create({required PlatformPlayerContent content, Map<String, Object>? params}) async {
    final playerId = await _pluginChannel.invokeMethod("pluginCreate", [content.toJson(), params]);
    return playerId;
  }

  @override
  Widget buildView(PlayerId playerId) {
    return AndroidView(
        viewType: 'nut_player_view_id',
        layoutDirection: TextDirection.ltr,
        creationParams: { 'playerId': playerId },
        creationParamsCodec: const StandardMessageCodec()
    );
  }

  @override
  Stream<VideoEvent> videoEventsFor(PlayerId playerId) {
    return _eventChannelFor(playerId)
        .receiveBroadcastStream()
        .map((event) {
      final Map<dynamic, dynamic> map = event as Map<dynamic, dynamic>;
      switch (map['event']) {
        case 'initialized':
          return VideoEvent(
            eventType: VideoEventType.initialized,
          );
        case 'ready':
          return VideoEvent(
            eventType: VideoEventType.ready,
            duration: Duration(milliseconds: map['duration'] as int),
          );
        case 'completed':
          return VideoEvent(
            eventType: VideoEventType.completed,
          );
        case 'bufferingUpdate':
          final List<dynamic> values = map['values'] as List<dynamic>;

          return VideoEvent(
            buffered: values.map<DurationRange>(_toDurationRange).toList(),
            eventType: VideoEventType.bufferingUpdate,
          );
        case 'bufferingStart':
          return VideoEvent(eventType: VideoEventType.bufferingStart);
        case 'bufferingEnd':
          return VideoEvent(eventType: VideoEventType.bufferingEnd);
        case 'isPlayingStateUpdate':
          return VideoEvent(
            eventType: VideoEventType.isPlayingStateUpdate,
            isPlaying: map['isPlaying'] as bool,
          );
        default:
          return VideoEvent(eventType: VideoEventType.unknown);
      }
    });
  }

  @override
  Future<void> play(PlayerId playerId) {
    return _pluginChannel.invokeMethod("pluginPlay", [playerId]);
  }

  @override
  Future<void> pause(PlayerId playerId) {
    return _pluginChannel.invokeMethod("pluginPause", [playerId]);
  }

  @override
  Future<int> state(PlayerId playerId) async {
    final result = await _pluginChannel.invokeMethod("pluginGetState", [playerId]);
    return 0;
  }

  @override
  Future<double> volume(PlayerId playerId) async {
    final result = await _pluginChannel.invokeMethod("pluginGetVolume", [playerId]);
    return result;
  }

  @override
  Future<void> setVolume(PlayerId playerId, double volume) {
    return _pluginChannel.invokeMethod("pluginSetVolume", [playerId, volume]);
  }

  @override
  Future<void> seek(PlayerId playerId, Duration position) {
    return _pluginChannel.invokeMethod("pluginSeek", [playerId, position.inMilliseconds]);
  }

  @override
  Future<void> setPlaybackSpeed(PlayerId playerId, double speed) {
    return _pluginChannel.invokeMethod("pluginSetPlaybackSpeed", [playerId, speed]);
  }

  @override
  Future<Duration> getPosition(PlayerId playerId) async {
    final result = await _pluginChannel.invokeMethod("pluginGetPosition", [playerId]);
    return Duration(milliseconds: result);
  }

  @override
  Future<void> setMixWithOthers(bool mixWithOthers) {
    throw UnimplementedError('setMixWithOthers() has not been implemented.');
  }

  @override
  Stream<String> logStream() {
    return _logStream;
  }

  DurationRange _toDurationRange(dynamic value) {
    final List<dynamic> pair = value as List<dynamic>;
    return DurationRange(
      Duration(milliseconds: pair[0] as int),
      Duration(milliseconds: pair[1] as int),
    );
  }
}
