import 'package:fis_measure/interfaces/process/items/item.dart';
import 'package:fis_measure/interfaces/process/items/item_feature.dart';
import 'package:fis_measure/interfaces/process/items/measure_terms.dart';
import 'package:fis_measure/interfaces/process/visuals/visual_area.dart';
import 'package:fis_measure/interfaces/process/visuals/visual.dart';
import 'package:fis_measure/interfaces/process/viewports/viewport.dart';
import 'package:fis_measure/interfaces/process/modes/mode.dart';
import 'package:fis_common/event/event_type.dart';
import 'package:fis_measure/interfaces/process/workspace/application.dart';
import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
import 'package:fis_measure/process/primitives/ellipse.dart';
import 'package:fis_measure/process/primitives/straightline.dart';
import 'package:flutter/painting.dart';
import 'package:vid/us/vid_us_image.dart';
import 'package:vid/us/vid_us_probe.dart';
import 'package:vid/us/vid_us_unit.dart';

import '../visual/visual.dart';

class Application implements IApplication {
  late VidUsProbe _probe;
  VidUsImage? _frame;
  List<IVisual>? _visuals;
  IMeasureItem? _activeItem;
  bool _canMeasure = false;
  Size _displaySize = Size.zero;
  final Set<IMeasureItem> _items = {};

  Application(VidUsProbe probe) {
    _probe = probe;
  }

  @override
  bool get canMeasure => _canMeasure;
  @override
  set canMeasure(bool value) {
    if (value != _canMeasure) {
      _canMeasure = value;
      _doCanMeasureChanged();
    }
  }

  @override
  String get applicationName => _probe.application.applicationName;

  @override
  String get categoryName => _probe.application.applicationCategoryName;

  @override
  IMode get currentMode => currentVisualArea.mode;

  @override
  IViewPort get currentViewPort => currentVisualArea.viewport!;

  @override
  IVisual get currentVisual => visuals.firstWhere((e) => e.activeArea != null);

  @override
  IVisualArea get currentVisualArea => currentVisual.activeArea!;

  @override
  VidUsImage? get frameData => _frame;

  @override
  List<IVisual> get visuals => _visuals!;

  @override
  Set<IMeasureItem> get items => _items;

  @override
  Size get displaySize => _displaySize;
  @override
  set displaySize(Size value) {
    if (value != _displaySize) {
      _displaySize = value;
    }
  }

  @override
  List<IMode> get avaliableModes {
    final modes = <IMode>[];
    for (var visual in visuals) {
      modes.addAll(visual.modes);
    }
    return modes;
  }

  @override
  IMeasureItem? get activeItem => _activeItem;
  set activeItem(IMeasureItem? value) {
    if (value != _activeItem) {
      _activeItem?.featureUpdated.removeListener(_onActiveItemFeatureUpdated);
      _activeItem = value;
      if (_activeItem != null) {
        _items.add(_activeItem!);
        _activeItem!.featureUpdated.addListener(_onActiveItemFeatureUpdated);
      }
      activeItemChanged.emit(this, value);
      _doMeasureRerender();
    }
  }

  @override
  var currentModeChanged = FEventHandler<IMode>();

  @override
  var visualAreaChanged = FEventHandler<IVisualArea>();

  @override
  var canMeasureChanged = FEventHandler<bool>();

  @override
  var activeItemChanged = FEventHandler<IMeasureItem?>();

  @override
  var measureRerenderReady = FEventHandler<void>();

  @override
  void loadFrame(VidUsImage frame) {
    bool frameLoaded = _frame != null;
    _frame = frame;
    if (!frameLoaded && canMeasure) {
      _loadVisuals();
    }
  }

  @override
  PointInfo createPointInfo(Offset offset, PointInfoType type) {
    if (frameData == null) {
      throw NullThrownError();
    }
    final width = displaySize.width;
    final height = displaySize.height;
    final x = offset.dx / width;
    final y = offset.dy / height;
    final percentOffset = Offset(x, y);
    final info = PointInfo.fromOffset(percentOffset, type);
    info.hostVisualArea = visuals.first.visualAreas.first; // TODO: 找区域
    activeItem?.execute(info);
    return info;
  }

  @override
  void switchItemByName(String name) {
    if (name == MeasureTerms.Distance) {
      activeItem = StraightLine.createDistance(
        ItemMeta(
          MeasureTerms.Distance,
          {
            "Description": MeasureTerms.Distance,
            "BriefDescription": "D",
            "Unit": VidUsUnit.cm,
          },
        ),
      );
      return;
    }

    if (name == MeasureTerms.Perimeter) {
      activeItem = Ellipse(
        ItemMeta(
          MeasureTerms.Perimeter,
          {
            "Description": MeasureTerms.Perimeter,
            "BriefDescription": MeasureTerms.Perimeter,
            "Unit": VidUsUnit.cm,
          },
        ),
        null,
      );
      return;
    }
    if (name == MeasureTerms.Area) {
      activeItem = Ellipse(
        ItemMeta(
          MeasureTerms.Area,
          {
            "Description": MeasureTerms.Area,
            "BriefDescription": MeasureTerms.Area,
            "Unit": VidUsUnit.cm2,
          },
        ),
        null,
      );
      return;
    }
  }

  void _doMeasureRerender() {
    measureRerenderReady.emit(this, null);
  }

  void _doCanMeasureChanged() {
    canMeasureChanged.emit(this, canMeasure);

    _clearViewPorts();
    if (canMeasure && frameData != null) {
      _loadVisuals();
    }
  }

  void _onActiveItemFeatureUpdated(Object sender, IMeasureItemFeature? e) {
    _doMeasureRerender();
  }

  void _loadVisuals() {
    _visuals = [];
    for (final data in frameData!.visuals) {
      _visuals!.add(VisualBase(data));
    }
  }

  void _clearViewPorts() {
    _visuals = null;
  }
}
