import 'package:common_pub/model/control_point.dart';
import 'package:common_pub/model/distance.dart';
import 'package:common_pub/model/pace.dart';
import 'package:common_pub/model/position.dart';
import 'package:common_pub/service/controller.dart';
import 'package:common_pub/ui/map_view/map_view.dart';
import 'package:common_pub/ui/map_view/view_map_trace.dart';
import '../logger.dart';
import '../service/api.dart' as pb;
import 'package:fixnum/fixnum.dart';

typedef MapId = Int64;

class Flag {
  Flag(this.value);
  int value;
  Color get color => Color(value);

  @override
  bool operator ==(Object other) {
    if (other is Flag) {
      return value == other.value;
    }
    return false;
  }

  @override
  int get hashCode => value.hashCode;

  static final red = Flag(0xffff0000);
  static final yellow = Flag(0xffffcb00);
  static final blue = Flag(0xff00a0ff);
  static List<Flag> get values => [red, yellow, blue];
}

class ActiveInfo {
  var id = 0;
  var name = '';
  var cpAllCount = 0;
  var userList = <UserInfo>[];
  final isHide = false.obs;

  UserInfo? getUserById(int id) {
    for (final one in userList) {
      if (one.gameInfo.userId == id) {
        return one;
      }
    }

    return null;
  }

  Future<UserInfo> newUserInfo(pb.ToOrienteerInGameInfo info) async {
    final r = await pb.ApiService.to.stub.toUserInActionBasicQuery(
        pb.ToUserInActionBasicQueryRequest(actId: id, userId: info.userId));

    final user = UserInfo()
      ..routeInfo = r.courseBaseInfo
      ..userInfo = r.baseInfo;
    await user.setGameInfo(info);

    return user;
  }

  Future<void> update(pb.ToActionInfo info) async {
    final newUserList = <UserInfo>[];

    for (final nUser in info.userList) {
      late UserInfo user;
      final oUser = getUserById(nUser.userId);
      if (oUser != null) {
        user = oUser;
        await user.update(nUser);
      } else {
        user = await newUserInfo(nUser);
      }

      newUserList.add(user);
    }
    userList = newUserList;
  }
}

extension ActiveInfoExt on pb.ToActionInfo {
  Future<ActiveInfo> into() async {
    final info = await pb.ApiService.to.stub
        .toActionBasicQuery(pb.IdRequest(id: Int64(actId)));

    final out = ActiveInfo()
      ..id = actId
      ..name = info.actName
      ..cpAllCount = info.totalControlNum;

    for (final one in userList) {
      out.userList.add(await out.newUserInfo(one));
    }

    return out;
  }
}

class UserInfo {
  final isHide = false.obs;
  String get name => userInfo.name;
  String get routeName => routeInfo.courseName;
  Pace get pace => Pace.perKm(gameInfo.gpsInfo.pace.seconds);
  Duration get duration =>
      DateTime.now().difference(gameInfo.gameSaveInfo.startAt.toModel());
  Distance get distance => Distance(m: gameInfo.gpsInfo.distance.toDouble());
  int get id => gameInfo.userId;
  var gameInfo = pb.ToOrienteerInGameInfo();
  var routeInfo = pb.CourseBaseInfo();
  var userInfo = pb.OrienteerBaseInfo();
  var trace = <TracePoint>[].obs;
  var flag = Flag.red.obs;
  DateTime? get startAt => gameInfo.gameSaveInfo.hasStartAt()
      ? gameInfo.gameSaveInfo.startAt.toModel()
      : null;
  var cpList = <ControlPoint>[];
  ControlPoint? nextWant;

  Distance get nextDistance {
    final one = nextWant;
    if (one != null) {
      final p1 = one.position;
      final p22 = gameInfo.gpsInfo.gameGpsInfos.lastOrNull;
      if (p22 != null) {
        final p2 = Position(longitude: p22.longitude, latitude: p22.latitude);
        return p1.distance(p2);
      }
    }
    return const Distance(m: 1000);
  }

  String get nextCPSN {
    return nextWant?.snString ?? '';
  }

  Future<void> update(pb.ToOrienteerInGameInfo info) async {
    final map = MapWatchService.instance;

    await setGameInfo(info);
    final indexMap = <int, TracePoint>{};

    for (final one in trace) {
      indexMap[one.ts.inMilliseconds] = one;
    }

    for (final one in info.gpsInfo.gameGpsInfos) {
      final t = one.gpsTime.toModel();
      final startAt = gameInfo.gameSaveInfo.startAt.toModel();
      final ts = t.difference(startAt);
      if (ts.inMilliseconds > 0 && !indexMap.containsKey(ts.inMilliseconds)) {
        final pos = one.toModel();
        final oneTrace = TracePoint()
          ..ts = ts
          ..position = pos;
        if (map!.plugMap.isInitFinish){
          oneTrace.onMap =
            await map.plugMap.gameMap.worldToPixel(oneTrace.position);
        }

        trace.add(oneTrace);
      }
    }

    // if (map!.plugMap.isInitFinish) {
    //   for (final one in trace) {
    //     if (one.onMap == Offset.zero) {
    //       one.onMap = await map.plugMap.gameMap.worldToPixel(one.position);
    //     }
    //   }
    // }
  }

  Future<void> setGameInfo(pb.ToOrienteerInGameInfo info) async {
    final map = MapWatchService.instance;
    gameInfo = info;
    cpList.clear();
    final cpMap = <int, ControlPoint>{};
    for (var (i, src) in routeInfo.controlPointSortedList.indexed) {
      final one = src.toModel()..sn = i.toString();
      if (map != null) {
        one.onMap = await map.plugMap.gameMap.worldToPixel(one.position);
      }

      cpList.add(one);
      cpMap[one.intId.toInt()] = one;
    }
    if (cpList.isNotEmpty) {
      cpList.first.isStart = true;
      cpList.last.isFinish = true;
    }
    var index = 0;
    for (var cp in gameInfo.gameSaveInfo.checkedSortedList) {
      if(cp.isCheckSuccess){
        for(var i = index; i < cpList.length; i++){
          final want = cpList[i];
          if(want.intId == cp.controlPointId){
            want.isSuccess=true;
            index++;
            break;
          }
        }
      }



      // cpMap[cp.controlPointId.toInt()]!.isSuccess = cp.isCheckSuccess;
    }

    for (var cp in cpList) {
      if (!cp.isSuccess) {
        cp.isNext = true;
        nextWant = cp;
        break;
      }
    }
  }
}

class MapWatchService extends PlugController {
  static final Rx<MapWatchService?> _instance = Rx(null);
  static MapWatchService? get instance => _instance.value;

  static Future<void> setMapById(MapId id) async {
    final info =
        await pb.ApiService.to.stub.toMapDetailV2(pb.IdRequest()..id = id);

    final thisInstance = MapWatchService(id: id)
      ..name = info.mapName
      ..plugMap.gameMap = info.zipImage.toGameMap();

    thisInstance.addPlugs([thisInstance.plugMap]);

    _instance.value = thisInstance;
    thisInstance.init();
    thisInstance.workFlushData();
  }

  Future<void> workFlushData() async {
    while (isActive) {
      try {
        await flushData();
      } catch (e) {
        error(e);
      }
      await 1.seconds.delay();
    }
  }

  ActiveInfo? getActiveById(int id) {
    for (final one in activeList) {
      if (one.id == id) {
        return one;
      }
    }
    return null;
  }

  Future<void> flushData() async {
    final r = await pb.ApiService.to.stub
        .toUserDetailQueryV2(pb.ToUserDetailQueryRequestV2(mapId: id.toInt()));

    final newList = <ActiveInfo>[];

    for (final one in r.list) {
      late ActiveInfo info;
      final old = getActiveById(one.actId);
      if (old != null) {
        info = old;
        await info.update(one);
      } else {
        info = await one.into();
      }
      newList.add(info);
    }

    activeList.value = newList;
  }

  MapWatchService({required this.id});
  final MapId id;
  String name = '';
  final plugMap = PlugMap();
  final activeList = <ActiveInfo>[].obs;
}
