import 'dart:io';
import 'dart:isolate';
import 'dart:async';

import 'package:dart_framework/data/isolate_data.dart';
import 'package:dart_framework/platform/process.dart';
import 'package:dart_framework/utils/system_util.dart' as system;

/// ********************** Test ************************* ///
void test() {
  var initData = {"param0": "value0"};
  var handler = IsolateManager.create<IsolateTemplate>(IsolateTemplate(),
      initData: initData);

  handler.addEventListener(IsolateEvent.ready, (data) async {
    handler.broadcast('test1', 'initData1');

    await Future.delayed(Duration(seconds: 2));

    handler.send('test2', message: 'data2');
    //will not arrive, addListener once
    handler.send('test1', message: 'data1');

    await Future.delayed(Duration(seconds: 5));

    handler.exit();
  });
  handler.addEventListener(
      IsolateEvent.exit, (data) => print('[Handler] Isolate is exited.'));
  handler.addListener("CutomMessage", (data) {
    print('[Handler] OnMessage - data: $data');
  });
}

class IsolateTemplate extends IsolateBase {
  @override
  void onReady(Object? initData) async {
    print(
        '[IsolateTemplate] appName: ${system.appName}, appID: ${system.appIdendifier}');
    print('[IsolateTemplate] onReady - initData: $initData');
    addEventListener('test1', onTest1Handler, executeOnce: true);
    addEventListener('test2', onTest2Handler);
  }

  void onTest1Handler(Object? data) async {
    print('[IsolateTemplate] OnTest1Handler: $data');
    var dir = Platform.isWindows ? 'dir' : 'ls -l';
    ProcessExecutor.run(StringBuffer(dir));
    await delayTest();
  }

  Future delayTest() async {
    await Future.delayed(Duration(seconds: 5));
    send(IsoMessege('CutomMessage', data: 'This is message'));
  }

  void onTest2Handler(Object? data) {
    print('[IsolateTemplate] OnTest2Handler: $data');
  }
}

/// ********************** Define *********************** ///
class _IsolateInnerEvent {
  static const String ready = '_IsolateInnerEvent.Ready';
  static const String exit = '_IsolateInnerEvent.Exit';
}

class _IsolateConstructData {
  late int id;
  late SendPort mainIsoSender;
  late Object? initData;
  late String appName;
  late String appID;
  _IsolateConstructData(
      this.id, this.mainIsoSender, this.appName, this.appID, this.initData);
}

class IsolateHandler {
  final int _id;
  final Map<String, List<void Function(Object?)>> _listenerMap = {};
  final Map<IsolateEvent, List<void Function(Object?)>> _listenerEventMap = {};
  IsolateHandler(this._id);

  void send(String type, {Object? message}) {
    IsolateManager.send(_id, IsoMessege(type, data: message));
  }

  void addListener(String type, void Function(Object?) func) {
    removeListener(type, func);
    if (!_listenerMap.containsKey(type)) {
      _listenerMap[type] = [];
    }
    _listenerMap[type]?.add(func);
  }

  void removeListener(String type, void Function(Object? data) func) {
    if (_listenerMap.containsKey(type)) {
      _listenerMap[type]?.remove(func);
    }
  }

  void addEventListener(IsolateEvent event, void Function(Object? data) func) {
    removeEventListener(event, func);
    if (!_listenerEventMap.containsKey(event)) {
      _listenerEventMap[event] = [];
    }
    _listenerEventMap[event]?.add(func);
  }

  void removeEventListener(
      IsolateEvent event, void Function(Object? data) func) {
    if (_listenerEventMap.containsKey(event)) {
      _listenerEventMap[event]?.remove(func);
    }
  }

  void broadcast(String type, Object? data) {
    IsolateManager.sendBroadcast(IsoMessege(type, data: data));
  }

  void _isolateEventHandler(IsolateEvent event, Object? data) {
    if (_listenerEventMap.containsKey(event)) {
      var list = _listenerEventMap[event];
      for (var item in list!) {
        item(data);
      }
    }
  }

  void _isolateMessageHandler(IsoMessege message) {
    var type = message.type;
    if (_listenerMap.containsKey(type)) {
      var list = _listenerMap[type];
      for (var item in list!) {
        item(message.data);
      }
    }
  }

  void exit() {
    IsolateManager.exit(_id);
  }
}

class IsolateManager {
  IsolateManager._privateConstructor();
  static final IsolateManager _instance = IsolateManager._privateConstructor();
  static IsolateManager get instance => _instance;

  static int _nonce = 0;
  static int get nonce {
    if (_nonce == double.maxFinite.toInt()) {
      _nonce = 0;
    } else {
      _nonce++;
    }
    return _nonce;
  }

  final Map<int, IsolateBase> _isoMap = {};
  final Map<int, Map<String, Function(Object?)>> _innerEventMap = {};

  static IsolateHandler create<T extends IsolateBase>(T inst,
      {Object?
          initData} /*,
      Function(IsolateEvent, Object?) isolateEventListener,
      Function(IsoMessege) messageListener*/
      ) {
    var id = nonce;
    var handler = IsolateHandler(id);
    instance._create<T>(inst, initData, id, handler._isolateEventHandler,
        handler._isolateMessageHandler);
    return handler;
  }

  void _create<T extends IsolateBase>(
      T inst,
      Object? initData,
      int id,
      Function(IsolateEvent, Object?) isolateEventListener,
      Function(IsoMessege) messageListener) async {
    final isoListener = ReceivePort();
    inst.id = id;
    _isoMap[id] = inst;
    _addInnerEvent(id, _IsolateInnerEvent.ready, (message) {
      inst.isoListener = message as SendPort;
      isolateEventListener(IsolateEvent.ready, id);
    });
    await Isolate.spawn(
        inst.initialize,
        _IsolateConstructData(id, isoListener.sendPort, system.appName,
            system.appIdendifier, initData));
    await for (IsoMessege message in isoListener) {
      if (message.type == _IsolateInnerEvent.exit) {
        isolateEventListener(IsolateEvent.exit, null);
        break;
      } else if (_filterInnerEvent(id, message)) {
        messageListener(message);
      }
    }
    isoListener.close();
    _isoMap.remove(id);
  }

  void _addInnerEvent(int id, String type, Function(Object?) listener) {
    Map<String, Function(Object?)>? eventMap;
    if (!_innerEventMap.containsKey(id)) {
      eventMap = {};
      _innerEventMap[id] = eventMap;
    }
    eventMap![type] = listener;
  }

  bool _filterInnerEvent(int id, IsoMessege message) {
    var isNotExist = true;
    if (_innerEventMap.containsKey(id) &&
        _innerEventMap[id]!.containsKey(message.type)) {
      isNotExist = false;
      _innerEventMap[id]![message.type]!(message.data);
    }
    return isNotExist;
  }

  static void sendBroadcast(IsoMessege message) {
    var entries = instance._isoMap.entries;
    for (var entry in entries) {
      entry.value.insertEvent(message);
    }
  }

  static void send(int id, IsoMessege message) {
    var map = instance._isoMap;
    if (map.containsKey(id)) {
      map[id]?.insertEvent(message);
    }
  }

  static void exit(int id) {
    var map = instance._isoMap;
    if (map.containsKey(id)) {
      map[id]!.insertEvent(IsoMessege(_IsolateInnerEvent.exit));
    }
  }
}

class _IsolateEventListener {
  late bool executeOnce;
  late Function(Object?) listener;
  _IsolateEventListener(this.listener, this.executeOnce);
}

class IsolateBase {
  late int id;
  late ReceivePort _mainIsoListener;
  late SendPort _mainIsoSender;
  late SendPort isoListener;
  final Map<String, List<_IsolateEventListener>> _eventMap = {};
  final List<_IsolateEventListener> _removeList = [];

  // ignore: library_private_types_in_public_api
  Future<void> initialize(_IsolateConstructData initData) async {
    id = initData.id;
    _mainIsoListener = ReceivePort();
    _mainIsoSender = initData.mainIsoSender;
    system.initialize(initData.appName, initData.appID);

    send(IsoMessege(_IsolateInnerEvent.ready, data: _mainIsoListener.sendPort));
    onReady(initData.initData);
    await for (IsoMessege message in _mainIsoListener) {
      if (message.type == _IsolateInnerEvent.exit) {
        send(IsoMessege(_IsolateInnerEvent.exit));
        break;
      } else {
        _onEvent(message);
      }
    }
    Isolate.exit(_mainIsoSender);
  }

  void onReady(Object? initData) async {}

  void send(IsoMessege message) {
    _mainIsoSender.send(message);
  }

  void _onEvent(IsoMessege message) {
    if (_eventMap.containsKey(message.type)) {
      var list = _eventMap[message.type]!;
      for (var item in list) {
        item.listener(message.data);
        if (item.executeOnce) {
          _removeList.add(item);
        }
      }
      if (_removeList.isNotEmpty) {
        for (var item in _removeList) {
          removeEventListener(message.type, item.listener);
        }
        _removeList.clear();
      }
    }
  }

  void addEventListener(String type, Function(Object?) listener,
      {bool executeOnce = false}) {
    List<_IsolateEventListener> list;
    if (_eventMap.containsKey(type)) {
      list = _eventMap[type]!;
    } else {
      list = [];
      _eventMap[type] = list;
    }
    list.add(_IsolateEventListener(listener, executeOnce));
  }

  void removeEventListener(String type, Function(Object?) listener) {
    if (_eventMap.containsKey(type)) {
      var list = _eventMap[type]!;
      for (int i = 0; i < list.length; ++i) {
        var item = list[i];
        if (item.listener == listener) {
          list.removeAt(i);
          --i;
        }
      }
    }
  }

  void insertEvent(IsoMessege message) {
    isoListener.send(message);
  }

  void exit() {
    IsolateManager.exit(id);
  }
}
