import 'dart:async';
import 'dart:collection';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/hash/hash.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/visual_feast/engine/action/frame_action.dart';
import 'package:guru_utils/visual_feast/visual_feast_animation.dart';
import 'package:guru_utils/visual_feast/visual_feast_completer.dart';
import 'package:guru_utils/visual_feast/visual_feast_widget.dart';
import 'package:lottie/lottie.dart';

import 'dart:ui' as ui show Image;

/// Created by Haoyi on 2022/7/5

part 'layers/layers.dart';

part 'layers/render_layer.dart';

part 'layers/sorting_layer.dart';

part 'render/render_element.dart';

part 'render/render_queue.dart';

part 'render/visual_feast_render.dart';

part 'sprite/visual_feast_sprite.dart';

class VisualFeastDirector extends Director {
  final FrameActionQueue frameActionQueue = FrameActionQueue();
  VisualFeastEngine? _runningEngine;
  final DoubleLinkedQueue<VisualFeastEngine> waitingEngine = DoubleLinkedQueue();

  final DoubleLinkedQueue<VisualFeastEngine> immediateEngine = DoubleLinkedQueue();
  final DoubleLinkedQueue<VisualFeastEngine> _tempImmediateEngine = DoubleLinkedQueue();

  final Map<String, VisualFeastSprite> spriteMap = {};

  final Map<String, LottieDrawable> lottieMap = {};

  void addSprite(String key, VisualFeastSprite sprite) {
    spriteMap[key] = sprite;
  }

  VisualFeastSprite getSprite(String key) {
    return spriteMap[key] ?? VisualFeastSprite.invalid();
  }

  void addLottie(String key, LottieDrawable drawable) {
    lottieMap[key] = drawable;
  }

  LottieDrawable getLottie(String key) {
    return lottieMap[key]!;
  }

  void deliverFrameAction(VoidCallback callback) {
    Log.d("deliverFrameAction isStart:$isStarted");
    if (isStarted) {
      frameActionQueue.deliverFrameAction(FrameAction(callback));
    } else {
      callback.call();
    }
  }

  void dispatchFrameAction(double t) {
    frameActionQueue.dispatchFrameAction(t);
  }

  void enqueueEngine(VisualFeastEngine engine) {
    engine.state = VisualFeastEngineState.waiting;
    waitingEngine.add(engine);
    Log.d("enqueueEngine: isActive:$isActive isPaused:$isPaused state:${engine.state}");
    if (!isActive) {
      start();
    }
    if (isPaused) {
      resume();
    }
  }

  void runEngine(VisualFeastEngine engine) {
    engine.state = VisualFeastEngineState.waiting;
    immediateEngine.add(engine);
    if (!isActive) {
      start();
    }
    if (isPaused) {
      resume();
    }
  }

  void updateEngine(double delta) {
    _tempImmediateEngine.clear();
    bool immediateRunning = false;
    for (var engine in immediateEngine) {
      if (!engine.isCompleted) {
        engine.state = VisualFeastEngineState.started;
        engine.frameUpdate(delta);
        _tempImmediateEngine.add(engine);
        immediateRunning |= true;
      } else {
        engine.dispose();
      }
    }
    immediateEngine.clear();
    immediateEngine.addAll(_tempImmediateEngine);

    final engine = _runningEngine;
    if (engine != null) {
      if (!engine.isCompleted) {
        engine.frameUpdate(delta);
        return;
      } else {
        engine.dispose();
      }
    }

    if (waitingEngine.isNotEmpty) {
      final newEngine = waitingEngine.removeFirst();
      if (!newEngine.isCompleted) {
        newEngine.state = VisualFeastEngineState.started;
        newEngine.frameUpdate(delta);
        _runningEngine = newEngine;
      }
    } else {
      _runningEngine = null;
      if (!immediateRunning) {
        pause();
      }
    }
  }

  @override
  void frameUpdate(double delta) {
    updateEngine(delta);
    dispatchFrameAction(delta);
  }

  @override
  void render(Canvas canvas) {
    for (var engine in immediateEngine) {
      engine.render(canvas);
    }
    _runningEngine?.render(canvas);
  }

  @override
  void dispose() {
    spriteMap.clear();
    super.dispose();
  }

  VisualFeastEngine createEngine({VoidCallback? onCompleted}) {
    return VisualFeastEngine._(this, onCompleted: onCompleted);
  }
}

enum VisualFeastEngineState { idle, waiting, started, completed }

class VisualFeastEngine {
  final LayerManager layerMgr = LayerManager();
  dynamic params;
  final CompositeSubscription subscriptions = CompositeSubscription();
  late VisualFeastCompleter completer;
  final VisualFeastDirector director;
  VoidCallback? _onCompleted;

  bool get isCompleted => state == VisualFeastEngineState.completed;
  VisualFeastEngineState state = VisualFeastEngineState.idle;

  void addSprite(String key, VisualFeastSprite sprite) {
    director.addSprite(key, sprite);
  }

  VisualFeastSprite getSprite(String key) {
    return director.getSprite(key);
  }

  void addLottie(String key, LottieDrawable lottie) {
    director.addLottie(key, lottie);
  }

  LottieDrawable getLottie(String key) {
    return director.getLottie(key);
  }

  VisualFeastEngine._(this.director, {VoidCallback? onCompleted})
      : _onCompleted = onCompleted,
        super() {
    completer = VisualFeastCompleter(onComplete: () {
      Log.d("visual feast engine complete! mark!!");
      state = VisualFeastEngineState.completed;
    });
  }

  void attachRenders(List<VisualFeastRender> renders) {
    for (var render in renders) {
      render._engine = this;
      layerMgr.attachRenderElement(render);
    }
  }

  void detachRenders(List<VisualFeastRender> renders) {
    for (var render in renders) {
      layerMgr.detachRenderElement(render);
      render._engine = null;
    }
  }

  void addSubscription(StreamSubscription? subscription) {
    if (subscription != null) {
      subscriptions.add(subscription);
    }
  }

  void disposeSubscriptions() {
    subscriptions.dispose();
  }

  void frameUpdate(double t) {
    layerMgr.frameUpdate(t);
  }

  void render(Canvas canvas) {
    layerMgr.render(canvas);
  }

  void onPrepare() {}

  void dispose() {
    disposeSubscriptions();
    layerMgr.detachAll();
    _onCompleted?.call();
    _onCompleted = null;
  }
}
