import 'dart:developer';

import 'package:collection/collection.dart';
import 'package:jiffy/jiffy.dart';
import 'package:tp5/core/core.dart';
import 'package:tp5/providers/airports.dart';
import 'package:tp5/roster/models/duty.dart';

import 'package:timezone/timezone.dart' as tz;

class Ftl {
  Duration minimumrest(String? station) =>
      Duration(hours: station == base ? 12 : 12, minutes: 0, seconds: 0);
  static Duration postflight = const Duration(minutes: 30);
  static Duration preflight = const Duration(minutes: 60);
  static Duration maxdutylast7 = const Duration(hours: 60);
  static Duration maxdutylast14 = const Duration(hours: 110);
  static Duration maxdutylast28 = const Duration(hours: 190);
  static Duration maxfltlast28 = const Duration(hours: 100);
  static Duration maxfltlastyear = const Duration(hours: 900);
  static Duration maxfltlast12 = const Duration(hours: 1000);
  Duration travelling(String station) {
    if (base == station) return Duration.zero;
    switch (station) {
      case "DSS":
        return const Duration(minutes: 60);
      case "ORY":
        return const Duration(minutes: 5);
      default:
        return const Duration(minutes: 30);
    }
  }

  String base;

  List<FtlDutyDetails> dutiesDetails = [];
  List<Duty> clDuties = [];
  List<FtlDuty> duties = [];

  // List<FtlDutyTotal> dutiesLength = [];

  List<DTInterval> get dutiesAsInterval => duties
      .map((e) =>
          (e.type != FtlDutyType.other) ? DTInterval(e.start!, e.end!) : null)
      .whereNotNull()
      .toList();
  List<DTInterval> get stdbyAsInterval => dutiesDetails
      .map((e) => (e.type == FtlDutyDetailsType.standby)
          ? DTInterval(e.start!, e.end!)
          : null)
      .whereNotNull()
      .toList();
  List<DTInterval> get fltsAsInterval => dutiesDetails
      .map((e) => (e.type == FtlDutyDetailsType.flight)
          ? DTInterval(e.start!, e.end!)
          : null)
      .whereNotNull()
      .toList();
  bool frms;
  Ftl({required this.dutiesDetails, required this.base, this.frms = false});
  Ftl.fromCrewlink(
      {required this.clDuties, required this.base, this.frms = false}) {
    for (Duty one in clDuties) {
      switch (one.type) {
        case "flight":
          dutiesDetails.add(
            FtlDutyDetails(
              start: one.start,
              end: one.end,
              placeStart: one.data["dep"],
              placeEnd: one.data["des"],
              type: FtlDutyDetailsType.flight,
              label: one.data["label"],
            ),
          );
        case "dhflight":
          dutiesDetails.add(
            FtlDutyDetails(
              start: one.start,
              end: one.end,
              placeStart: one.data["dep"],
              placeEnd: one.data["des"],
              type: FtlDutyDetailsType.dhflight,
              label: one.data["label"],
            ),
          );
        case "dhlimo":
          dutiesDetails.add(
            FtlDutyDetails(
              start: one.start,
              end: one.end,
              placeStart: one.data["dep"],
              placeEnd: one.data["des"],
              type: FtlDutyDetailsType.dhlimo,
              label: one.data["label"],
            ),
          );
        case "ground":
          if (["SBY1", "SBY2", "SBY3"].contains(one.data["label"])) {
            dutiesDetails.add(
              FtlDutyDetails(
                start: one.start,
                end: one.end,
                placeStart: one.data["dep"],
                // placeEnd: one.data["des"],
                type: FtlDutyDetailsType.standby,
                label: one.data["label"],
              ),
            );
          } else if (["R0"].contains(one.data["label"])) {
            //reserve is not duty
            dutiesDetails.add(
              FtlDutyDetails(
                start: one.start,
                end: one.end,
                placeStart: one.data["dep"],
                // placeEnd: one.data["des"],
                type: FtlDutyDetailsType.reserve,
                label: one.data["label"],
              ),
            );
          } else if ((one.data["actype"] ?? "") != "") {
            dutiesDetails.add(
              FtlDutyDetails(
                start: one.start,
                end: one.end,
                placeStart: one.data["dep"],
                // placeEnd: one.data["des"],
                type: FtlDutyDetailsType.sim,
                label: one.data["label"],
              ),
            );
          } else if (one.start != null &&
              one.end != null &&
              one.end!.diff(one.start!, unit: Unit.hour).abs() < 12) {
            dutiesDetails.add(
              FtlDutyDetails(
                start: one.start,
                end: one.end,
                placeStart: one.data["dep"],
                // placeEnd: one.data["des"],
                type: FtlDutyDetailsType.ground,
                label: one.data["label"],
              ),
            );
          } else {
            // print("ftl: constr: unknown ${one.date} ${one.type} ${one.data}");
          }
        case "checkin":
          dutiesDetails.add(
            FtlDutyDetails(
              start: one.start,
              end: one.end,
              placeStart: one.data["dep"],
              // placeEnd: one.data["des"],
              type: FtlDutyDetailsType.preflight,
            ),
          );

        case "checkout":
          dutiesDetails.add(
            FtlDutyDetails(
              start: one.start,
              end: one.end,
              // placeStart: one.data["dep"],
              placeEnd: one.data["des"],
              type: FtlDutyDetailsType.postflight,
            ),
          );
        case "credit":
        case "wholeday":
          break;
        default:
        // print("ftl: unk: ${one.date} ${one.type} ${one.data}");
      }
    }
  }

  Jiffy? _calcPostflight(FtlDutyDetails xduty) {
    if (xduty.end != null &&
        [FtlDutyDetailsType.flight, FtlDutyDetailsType.dhflight]
            .contains(xduty.type)) {
      return xduty.end!.addDuration(postflight);
    } else {
      return xduty.end;
    }
  }

  Jiffy? _calcPreflight(FtlDutyDetails xduty) {
    if (xduty.start != null &&
        [
          FtlDutyDetailsType.flight,
          FtlDutyDetailsType.dhflight,
          FtlDutyDetailsType.dhlimo,
          FtlDutyDetailsType.sim,
        ].contains(xduty.type)) {
      return (base == (xduty.placeStart ?? ""))
          ? xduty.start!.subtractDuration(const Duration(minutes: 60))
          : (xduty.start!.subtractDuration(preflight));
    } else {
      return xduty.start;
    }
  }

  final List<FtlDutyDetailsType> _fdpList = [
    FtlDutyDetailsType.flight,
    FtlDutyDetailsType.dhflight,
    FtlDutyDetailsType.dhlimo
  ];

  //calcul duties
  calcduties({Jiffy? date}) {
    log("FTL: start calcduties");
    duties.clear();

    final t1 = Jiffy.now();
    _calcDuty();

    log("FTL: start _calcfdpmax");
    _calcfdpmax();

    log("FTL: start _clacdutytotal");
    // _calcDutyTotal();

    log("FTL: start calclegal");
    _calcLegal(date: date);

    final t4 = Jiffy.now();
    print(
        "ftl: calcduties calculation :  ${t4.diff(t1, unit: Unit.second, asFloat: true)} ms");

    log("FTL: finish calcduties");

    // return [duties, dutiesDetails, dutiesLength];
    return [duties, dutiesDetails];
  }

  _calcDuty() {
    for (int i = 0; i < dutiesDetails.length; i++) {
      var last = i > 0 ? dutiesDetails[i - 1] : null;

      final checkin = ((last?.type == FtlDutyDetailsType.preflight)
          ? dutiesDetails[i - 1].start
          : null);

      final newduty =
          _addDutyDetail(index: i, duty: FtlDuty(), checkin: checkin);
      //print("${dutiesDetails[i].type} $newduty");
      if (newduty != null) {
        if (duties.isEmpty) {
          //first duty
          duties.add(newduty);
        } else if ((newduty.start!.isAfter(duties.last.end!)) &&
            (duties.last.type == FtlDutyType.other ||
                newduty.type == FtlDutyType.other)) {
          //last and new are not duty nor fdp (other=>stdby or rsrv)
          duties.add(newduty);
        } else if ((newduty.start!
                .subtractDuration(travelling(newduty.placeStart ?? base))
                .isAfter(
                  duties.last.end!
                      .addDuration(travelling(duties.last.placeEnd ?? base)),
                )) &&
            // duties.last.type == FtlDutyType.duty &&
            newduty.type == FtlDutyType.duty) {
          //last is duty
          duties.add(newduty);
        } else if (newduty.start!.diff(duties.last.end!, unit: Unit.minute) <
            minimumrest(duties.last.placeEnd).inMinutes) {
          //diff < minimumrest
          //ilhim
          final mergedduty = _addDutyDetail(
              index: i,
              duty: duties.isNotEmpty ? duties.last : FtlDuty(),
              checkin: checkin);
          duties[duties.length - 1] = mergedduty!;
        } else {
          //jdida
          duties.add(newduty);
        }
      }
    }
  }

  _calcfdpmax() {
    for (var i = 0; i < duties.length; i++) {
      FtlDuty duty = duties[i];
      FtlDuty? lastduty = i == 0 ? null : duties[i - 1];
      if (duty.type == FtlDutyType.fdp) {
        String acclim = _acclimatized(
            tzoffset: tzDiff(duty.placeStart!, lastduty?.placeEnd ?? base,
                duty.start!.dateTime),
            timeElapsed:
                lastduty?.end!.dateTime.difference(duty.start!.dateTime) ??
                    const Duration());
        duties[i].acclim = acclim;
        duties[i].reftime = changeTz(duty.start!, duty.placeStart ?? base).Hm;

        Jiffy reftime = duty.start!;
        if (duty.reportdelay2 != null && duty.reportdelay1 != null) {
          if (duty.start!.dateTime
                  .difference(duty.reportdelay2!.dateTime)
                  .inMinutes <
              (4 * 60)) {
            reftime = duty.start!;
            duty.start =
                duty.notification2!.add(hours: 1).min(duty.reportdelay1!);
          } else {
            reftime = duty.reportdelay2!;
            duty.start = duty.reportdelay2!;
          }
        } else if (duty.reportdelay1 != null) {
          if (duty.start!.dateTime
                  .difference(duty.reportdelay1!.dateTime)
                  .inMinutes <
              (4 * 60)) {
            reftime = duty.start!;
            duty.start = duty.reportdelay1!;
          } else {
            reftime = duty.reportdelay1!;
            duty.start = duty.reportdelay1!;
          }
        }

        switch (acclim) {
          case "D":
            // print("${duty.start?.yMMMd} ${duty.start?.toUtc().Hm}");
            duties[i].fdpMax = fdpMaxBasic(
                reftime: reftime, iata: duty.placeStart, sectors: duty.sectors);
            duties[i].fdpExtMax = fdpMaxExtBasic(
                reftime: reftime, iata: duty.placeStart, sectors: duty.sectors);
          case "B":
            duties[i].fdpMax = fdpMaxBasic(
                reftime: reftime,
                iata: lastduty?.placeEnd ?? lastduty?.placeStart ?? base,
                sectors: duty.sectors);
            duties[i].fdpExtMax = fdpMaxExtBasic(
                reftime: reftime,
                iata: lastduty?.placeEnd ?? lastduty?.placeStart ?? base,
                sectors: duty.sectors);
          case "X":
            if (frms) {
              duties[i].fdpMax = fdpMaxUnk(
                  reftime: reftime,
                  iata: duty.placeStart,
                  sectors: duty.sectors);
            } else {
              duties[i].fdpMax = fdpMaxUnkFrms(
                  reftime: reftime,
                  iata: duty.placeStart,
                  sectors: duty.sectors);
            }
            break;
          default:
        }
      }
    }
  }

  _calcLegal({Jiffy? date}) {
    if (date == null) {
      for (var i = 0; i < duties.length; i++) {
        _checklegal(index: i);
      }
    } else {
      duties.forEachIndexed((i, e) {
        if (duties[i]
            .start!
            .isSameOrAfter(date.startOf(Unit.day).subtract(days: 1))) {
          _checklegal(index: i);
        }
      });
    }
  }

  Duration _sumDuration(List<DTInterval> x) =>
      Duration(milliseconds: x.map((e) => e.duration.inMilliseconds).sum);

  List<DTInterval> breaks(FtlDuty duty) {
    List<DTInterval> out = [];
    for (var e in duty.dutiesDetailsIndex) {
      final dutydetail = dutiesDetails[e];
      final deptravel = (base == (dutydetail.placeStart ?? ""))
          ? Duration.zero
          : travelling(dutydetail.placeStart ?? "");
      final destravel = (base == (dutydetail.placeEnd ?? ""))
          ? Duration.zero
          : travelling(dutydetail.placeEnd ?? dutydetail.placeStart ?? "");
      final start = _calcPreflight(dutydetail)!.subtractDuration(deptravel);
      final finish = _calcPostflight(dutydetail)!.addDuration(destravel);
      if (finish.isAfter(start)) {
        out.add(DTInterval(start, finish));
      }
    }
    return duty.interval.minusmany(out);
  }

  _checklegal({required int index}) {
    FtlDuty duty = duties[index];

    final date = changeTz(duty.start!, base).endOf(Unit.day);

    // log("ftl: dutylenght date=${date.format(pattern: "ddMMMyy HHmm")} \n date-7=${date.subtract(days: 7 - 1).startOf(Unit.day).format(pattern: "ddMMMyy HHmm")}");
    // print("${date.yMEd}");
    final dutyLength = FtlDutyTotal(
        date: date, dutyLength: Duration.zero, fltLength: Duration.zero)
      ..dutylast7 = _sumDuration(
              DTInterval(date.subtract(days: 7 - 1).startOf(Unit.day), date)
                  .intersectionmany(dutiesAsInterval))
          .add(_sumDuration(
                  DTInterval(date.subtract(days: 7 - 1).startOf(Unit.day), date)
                      .intersectionmany(stdbyAsInterval))
              .multiply(0.25))
      ..dutylast14 = _sumDuration(
              DTInterval(date.subtract(days: 14 - 1).startOf(Unit.day), date)
                  .intersectionmany(dutiesAsInterval))
          .add(_sumDuration(DTInterval(
                      date.subtract(days: 14 - 1).startOf(Unit.day), date)
                  .intersectionmany(stdbyAsInterval))
              .multiply(0.25))
      ..dutylast28 = _sumDuration(
              DTInterval(date.subtract(days: 28 - 1).startOf(Unit.day), date)
                  .intersectionmany(dutiesAsInterval))
          .add(_sumDuration(DTInterval(
                      date.subtract(days: 28 - 1).startOf(Unit.day), date)
                  .intersectionmany(stdbyAsInterval))
              .multiply(0.25))
      ..fltlast28 = _sumDuration(
          DTInterval(date.subtract(days: 28 - 1).startOf(Unit.day), date)
              .intersectionmany(fltsAsInterval))
      ..fltlast12 = _sumDuration(DTInterval(
              date.endOf(Unit.month).subtract(months: 12).startOf(Unit.month),
              date)
          .intersectionmany(fltsAsInterval))
      ..fltyear = _sumDuration(DTInterval(date.startOf(Unit.year), date)
          .intersectionmany(fltsAsInterval));
    duty.dutyTotal = dutyLength;
    // print("${date.yMEd} ${dutyLength.fltyear?.tohhmm}");

    // dutylast7,
    if ((dutyLength.dutylast7?.inMinutes ?? 0) > maxdutylast7.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.dutylast7,
          legalCauseMsg:
              "Max duty in 7 days ending ${duty.start?.format(pattern: "ddMMMyy")} exceeded: ${dutyLength.dutylast7?.tohhmm} / ${maxdutylast7.inHours}h"));
    }
    // dutylast14,
    if ((dutyLength.dutylast14?.inMinutes ?? 0) > maxdutylast14.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.dutylast14,
          legalCauseMsg:
              "Max duty in 14 days ending ${duty.start?.format(pattern: "ddMMMyy")} exceeded: ${dutyLength.dutylast14?.tohhmm} / ${maxdutylast14.inHours}h"));
    }
    // dutylast28,
    if ((dutyLength.dutylast28?.inMinutes ?? 0) > maxdutylast28.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.dutylast28,
          legalCauseMsg:
              "Max duty in 28 days ending ${duty.start?.format(pattern: "ddMMMyy")} exceeded: ${dutyLength.dutylast28?.tohhmm} / ${maxdutylast28.inHours}h"));
    }
    // fltlast28,
    if ((dutyLength.fltlast28?.inMinutes ?? 0) > maxfltlast28.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.fltlast28,
          legalCauseMsg:
              "Max flight hours in 28 days ending ${duty.start?.format(pattern: "ddMMMyy")} exceeded: ${dutyLength.fltlast28?.tohhmm} / ${maxfltlast28.inHours}h"));
    }

    // fltyear,
    if ((dutyLength.fltyear?.inMinutes ?? 0) > maxfltlastyear.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.fltyear,
          legalCauseMsg:
              "Max flight hours in calendar year ${duty.start?.format(pattern: "yyyy")} exceeded: ${dutyLength.fltyear?.tohhmm} / ${maxfltlastyear.inHours}h"));
    }
    // fltlast12,
    if ((dutyLength.fltlast12?.inMinutes ?? 0) > maxfltlast12.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.fltlast12,
          legalCauseMsg:
              "Max flight hours in 12 months ending ${duty.start?.format(pattern: "ddMMMyy")} exceeded: ${dutyLength.fltlast12?.tohhmm} / ${maxfltlast12.inHours}h"));
    }

    // fdpmax,
    if (duty.type == FtlDutyType.fdp && duty.fdpMax != null) {
      if (duty.fdpLength.inMinutes <= duty.fdpMax!.inMinutes) {
      } else if (duty.fdpExtMax != null &&
          duty.fdpLength.inMinutes <= duty.fdpExtMax!.inMinutes) {
        int nbFdpExt = 0;
        for (var i = index;
            i >= 0 &&
                DTInterval(
                        changeTz(duty.start!, base)
                            .startOf(Unit.day)
                            .subtract(days: 7),
                        changeTz(duty.end!, base))
                    .isOverlap(DTInterval(duties[i].start!, duties[i].end!));
            i--) {
          if (duties[i].fdpExt) nbFdpExt++;
        }

        if (nbFdpExt < 2) {
          duty.fdpExt = true;
          duties[index] = duty;
        } else {
          //nbext more than2 in 7days
          duty.legals.add(FtlLegal(
              legalCause: FtlLegalCause.fdpmax,
              legalCauseMsg:
                  "FDP Ext used more than twice in 7 days. ${duty.fdpLength.tohhmm} / ${duty.fdpMax?.tohhmm} / ext${duty.fdpExtMax?.tohhmm}"));
        }
      } else if (breaks(duty).any((e) => (e.duration.inHours > 3)
          //&&
          // ((duty.fdpLength.inMinutes) <=
          //     (e.duration
          //         .multiply(0.5)
          //         .add(duty.fdpMax ?? Duration.zero)
          //         .inMinutes)))) {
          )) {
        //split duty check
        final fdpmaxbreak = breaks(duty)
            .map((e) => ((e.duration.inHours > 3)
                ? ((e.duration.multiply(0.5).add(duty.fdpMax ?? Duration.zero)))
                : null))
            .whereNotNull();
        if (fdpmaxbreak.isNotEmpty && duty.fdpLength < fdpmaxbreak.first) {
          duty.legals.add(FtlLegal(
              legalCause: FtlLegalCause.fdpmax,
              condition:
                  "A break on ground must be considered to extend FDP Max to ${fdpmaxbreak.first.tohhmm}, ",
              legalCauseMsg: "FDP is: ${duty.fdpLength.tohhmm}"));
        } else {
          duty.legals.add(FtlLegal(
              legalCause: FtlLegalCause.fdpmax,
              legalCauseMsg:
                  "FDP Max is excedeed even with ground break of ${breaks(duty).map((e) => ((e.duration.inHours > 3) ? (e.duration) : null)).whereNotNull().first.tohhmm}.  FDP:${duty.fdpLength.tohhmm}/Max:${fdpmaxbreak.first.tohhmm}"));
        }
      } else {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.fdpmax,
            legalCauseMsg:
                "FDP is ${duty.fdpLength.tohhmm}/Max:${duty.fdpMax?.tohhmm}"));
      }
    }
    // restfdp without ext,
    final FtlDuty? lastduty = (index > 0) ? duties[index - 1] : null;
    // log("ftl: checking duty <${duty}> lastduty ended <${lastduty}>");

    if (lastduty != null &&
        duty.type == FtlDutyType.fdp &&
        (lastduty.type == FtlDutyType.fdp ||
            lastduty.type == FtlDutyType.duty ||
            lastduty.dutiesDetailsIndex.any(
                (e) => dutiesDetails[e].type == FtlDutyDetailsType.standby))) {
      final rest = minimumrest(lastduty.placeEnd)
          .max(duty.end!.dateTime.difference(duty.start!.dateTime));
      duties[index - 1].restends = lastduty.end!.addDuration(rest);

      if (lastduty.restends!.isAfter(duty.start!)) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.restfdp,
            legalCauseMsg:
                "Need more rest before starting FDP. rest: ${duty.start!.dateTime.difference(lastduty.end!.dateTime).tohhmm} / ${rest.tohhmm}"));
      }
    }
    // restfdp with ext,
    duty = duties[index];
    FtlDuty? nextduty = (index < duties.length - 1) ? duties[index + 1] : null;
    if (duty.fdpExt) {
      final restbefore = (lastduty == null)
          ? null
          : minimumrest(lastduty.placeEnd)
              .max(lastduty.end!.dateTime.difference(lastduty.start!.dateTime));
      final restafter = (nextduty == null)
          ? null
          : minimumrest(duty.placeEnd)
              .max(duty.end!.dateTime.difference(duty.start!.dateTime));
//+2h rest before && 2h rest after
      if ((lastduty == null ||
              duty.start!.dateTime
                      .difference(lastduty.end!.dateTime)
                      .inMinutes >=
                  (restbefore?.inMinutes ?? 0) + (2 * 60)) &&
          (nextduty == null ||
              nextduty.start!.dateTime
                      .difference(duty.end!.dateTime)
                      .inMinutes >=
                  (restafter?.inMinutes ?? 0) + (60 * 2))) {
      }
//+4h rest after
      else if ((nextduty == null ||
          nextduty.start!.dateTime.difference(duty.end!.dateTime).inMinutes >=
              (restafter?.inMinutes ?? 0) + (60 * 4))) {
      }
//not enough rest
      else {
        duty.restends = duty.end!.addDuration(restafter!).add(hours: 4);
        duties[index] = duty;
        nextduty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.restfdp,
            legalCauseMsg:
                "Need more rest after ExtFDP before starting FDP on ${nextduty.start?.format(pattern: "ddMMMyy HH:mm")} rest: ${nextduty.start?.dateTime.difference(duty.end!.dateTime).tohhmm} / ${restafter.add(const Duration(hours: 4)).tohhmm}"));
        duties[index + 1] = nextduty;
      }
    }
    // restrecurrent,

    //   36h inc 2 local nights or (60h if 4 disruptive)
    const Duration maxdutyrecrest = Duration(hours: 168);
    Duration minrecrest = const Duration(hours: 36);

    DTInterval rest = DTInterval(duty.start!, duty.start!);
    var i = index;

    int nbdisruptive = 0;

    while (i >= 1 &&
        !(rest.duration.inMinutes >= const Duration(hours: 36).inMinutes &&
            rest.contains(DTInterval.fromHm(
                apartir: changeTz(rest.start, duties[i].placeStart!).toUtc(),
                h: 23,
                m: 0,
                duration: const Duration(hours: 7, minutes: 59),
                ap: duties[i].placeStart!)) &&
            rest.contains(DTInterval.fromHm(
                apartir:
                    changeTz(rest.start.add(hours: 24), duties[i].placeStart!)
                        .toUtc(),
                h: 23,
                m: 0,
                duration: const Duration(hours: 7, minutes: 59),
                ap: duties[i].placeStart!)))) {
      final thisduty = duties[i];

      if (_isEarlyStart(thisduty) ||
          _isLateFinish(thisduty) ||
          _isNightDuty(thisduty)) {
        //log("${thisduty}");
        nbdisruptive++;
        if (nbdisruptive == 4) {
          minrecrest = const Duration(hours: 60);
        }
      }
      i--;
      rest = DTInterval(duties[i].end!, duties[i + 1].start!);
    }

    if (DTInterval(rest.end, duty.end!).duration.inMinutes >
        maxdutyrecrest.inMinutes) {
      duty.legals.add(FtlLegal(
          legalCause: FtlLegalCause.restrecurrent,
          legalCauseMsg:
              "Before duty, need ${minrecrest.inHours}h rest including 2 local nights. from: ${rest.end.format(pattern: "ddMMMyy HH:mm")} ${DTInterval(rest.end, duty.end!).duration.tohhmm}/${maxdutyrecrest.tohhmm} "));
    }

    //   48h inc 2 local days

    final startmonth = changeTz(duty.start!.startOf(Unit.month), base);
    final endmonth = startmonth.endOf(Unit.month);
    if (duty.interval.isOverlap(
        DTInterval(endmonth.subtract(days: 4).add(minutes: 1), endmonth))) {
      int nb48inmonth = 0;
      List<FtlDuty> dutiesmonth = duties
          .where((e) => DTInterval(startmonth, endmonth)
              .isOverlap(DTInterval(e.start!, e.end!)))
          .toList();

      List<DTInterval> restmonth = DTInterval(startmonth, endmonth).minusmany(
          dutiesmonth
              .map((e) => DTInterval(changeTz(e.start!, e.placeStart!),
                  changeTz(e.end!, e.placeStart!)))
              .toList());
      // print(restmonth);

      while (restmonth.isNotEmpty) {
        final e = restmonth.removeAt(0);

        final localdays = DTInterval.fromHm(
            apartir: e.start.toUtc(),
            h: 0,
            m: 0,
            duration: const Duration(hours: 47, minutes: 59),
            ap: base);
        if (e.contains(localdays)) {
          nb48inmonth++;
          // print(
          //     "2localdays: ${localdays.start.format(pattern: "ddMMMyy HH:mm")} ${localdays.end.format(pattern: "ddMMMyy HH:mm")}");
          // print(
          //     "in rest ${e.start.format(pattern: "ddMMMyy HH:mm")} ${e.end.format(pattern: "ddMMMyy HH:mm")}");
          restmonth = [...e.minus(localdays), ...restmonth];
        }
      }

      if (nb48inmonth == 0) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.restrecurrent,
            legalCauseMsg:
                "Before duty, need 2x2 local days rest from: ${startmonth.format(pattern: "ddMMMyy HH:mm")} to:  ${endmonth.format(pattern: "ddMMMyy HH:mm")}"));
      } else if (nb48inmonth == 1 &&
          DTInterval(endmonth.subtract(days: 2).add(minutes: 1), endmonth)
              .isOverlap(duty.interval)) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.restrecurrent,
            legalCauseMsg:
                "Before duty, need 2x2 local days rest from: ${startmonth.format(pattern: "ddMMMyy HH:mm")} to:  ${endmonth.format(pattern: "ddMMMyy HH:mm")}"));
      } else if (nb48inmonth == 1 &&
          DTInterval(endmonth.subtract(days: 4).add(minutes: 1),
                  endmonth.subtract(days: 2))
              .isOverlap(duty.interval) &&
          DTInterval(endmonth.subtract(days: 4).add(minutes: 1),
                  endmonth.subtract(days: 2))
              .intersectionmany(
                  [...dutiesAsInterval, ...stdbyAsInterval]).isEmpty) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.restrecurrent,
            legalCauseMsg:
                "Before duty, need 2x2 local days rest from: ${startmonth.format(pattern: "ddMMMyy HH:mm")} to:  ${endmonth.format(pattern: "ddMMMyy HH:mm")}"));
      }
    }
    // disruptive
    duty.lateFinish = _isLateFinish(duty);
    duty.earlyStart = _isEarlyStart(duty);
    duty.nightDuty = _isNightDuty(duty);
    if (duty.fdpStart != null &&
        lastduty != null &&
        base == lastduty.placeEnd &&
        (_isLateFinish(lastduty) || _isNightDuty(lastduty)) &&
        _isEarlyStart(duty)) {
      // log("ftl: found late finish or nighty <${duty.start!.format(pattern: "ddMMMyy HH:mm")}");
      if (!DTInterval(lastduty.end!, duty.start!).contains(DTInterval.fromHm(
          apartir: changeTz(lastduty.end!, lastduty.placeEnd!).toUtc(),
          h: 23,
          m: 0,
          duration: const Duration(hours: 7, minutes: 59),
          ap: duty.placeStart!))) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.disruptive,
            legalCauseMsg:
                "Disruptive duty; one local night of rest is required before early departure."));
      }
    }

    //consecutive night duty
    if (_isConsecutiveNight(duty, lastduty)) {
      var i = index;
      final monthint = DTInterval(
          changeTz(duty.start!.startOf(Unit.month), base),
          changeTz(duty.start!.endOf(Unit.month), base));
      int nbconsnight = 0;
      while (i > 0 &&
          duties[i]
              .interval
              .isOverlap(changeTzDt(monthint, duty.placeStart!))) {
        if (_isConsecutiveNight(duties[i], duties[i - 1])) {
          nbconsnight++;
        }
        i--;
      }
      if (nbconsnight > 1) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.disruptive,
            legalCauseMsg:
                "Consecutive nights is allowed only once per civil month."));
      } else {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.disruptive,
            condition:
                "Two consecutive night duties; crew member agreement is required.",
            legalCauseMsg: ""));
      }
    }

    //local day rest if night duty
    if (lastduty != null && _isNightDuty(lastduty)) {
      if (DTInterval.fromHm(
              apartir: changeTz(lastduty.end!, lastduty.placeEnd!).toUtc(),
              h: 0,
              m: 0,
              duration: const Duration(hours: 23, minutes: 59),
              ap: lastduty.placeEnd!)
          .isOverlap(changeTzDt(duty.interval, duty.placeStart!))) {
        duty.legals.add(FtlLegal(
            legalCause: FtlLegalCause.disruptive,
            condition: "You can request a local day off after last night duty.",
            legalCauseMsg: ""));
      }
    }

    // if (duty.legals.isNotEmpty) {
    //   log("${duty.legals.map((e) => e.legalCauseMsg)}");
    // }
    // duties[index] = duty;
  }

  bool _isConsecutiveNight(FtlDuty duty, FtlDuty? lastduty) =>
      _isNightDuty(duty) &&
      lastduty != null &&
      DTInterval.fromHm(
              apartir: changeTz(lastduty.end!, lastduty.placeEnd!).toUtc(),
              h: 2,
              m: 0,
              duration: const Duration(hours: 2, minutes: 59),
              ap: lastduty.placeEnd!)
          .isOverlap(lastduty.interval.toUtc());

  FtlDuty? _addDutyDetail(
      {required int index, required FtlDuty duty, Jiffy? checkin}) {
    final one = dutiesDetails[index];
    if (_fdpList.contains(one.type)) {
      duty.placeStart = duty.placeStart ?? one.placeStart;
      duty.placeEnd = one.placeEnd;

      //duty.start = duty.start ?? _calcPreflight(one).latest(checkin);
      duty.start = duty.start ?? checkin ?? _calcPreflight(one);

      duty.end = _calcPostflight(one);
      if (one.type == FtlDutyDetailsType.flight) {
        duty.type = FtlDutyType.fdp;
        duty.sectors++;
        duty.fdpStart = duty.start;
        duty.fdpEnd = one.end;
      } else {
        duty.type = duty.type ?? FtlDutyType.duty;
      }
      duty.dutiesDetailsIndex.add(index);
    } else if ([FtlDutyDetailsType.standby, FtlDutyDetailsType.reserve]
        .contains(one.type)) {
      duty.placeStart = duty.placeStart ?? one.placeStart;
      duty.placeEnd = one.placeStart;
      duty.start = duty.start ?? _calcPreflight(one);
      duty.end = one.end;
      duty.type = FtlDutyType.other;
      duty.dutiesDetailsIndex.add(index);
    } else if ([FtlDutyDetailsType.ground, FtlDutyDetailsType.sim]
        .contains(one.type)) {
      duty.placeStart = duty.placeStart ?? one.placeStart;
      duty.placeEnd = one.placeStart;
      duty.start = duty.start ?? _calcPreflight(one);
      duty.end = one.end;
      duty.type = duty.type ?? FtlDutyType.duty;
      duty.dutiesDetailsIndex.add(index);
    } else {
      return null;
    }
    return duty;
  }

  static bool _isEarlyStart(FtlDuty x) {
//500 559
    // print(
    //     "$x is earlystart: ${DTInterval.fromHm(apartir: changeTz(x.start!, x.placeStart!), h: 5, m: 0, duration: const Duration(minutes: 59)).include(x.start!)}");
    return (x.fdpStart != null &&
        DTInterval.fromHm(
                apartir: changeTz(x.start!.subtract(hours: 24), x.placeStart!)
                    .toUtc(),
                h: 5,
                m: 0,
                duration: const Duration(minutes: 59),
                ap: x.placeStart!)
            .include(changeTz(x.start!, x.placeStart!)));
  }

  static bool _isLateFinish(FtlDuty x) {
//2300  159
    // print(
    //     "$x is latefinish: ${DTInterval.fromHm(apartir: changeTz(x.start!, x.placeStart!), h: 23, m: 0, duration: const Duration(hours: 2, minutes: 59)).include(x.end!)}");
    return (x.type != FtlDutyType.other) &&
        (DTInterval.fromHm(
                apartir: changeTz(x.start!, x.placeStart!).toUtc(),
                h: 23,
                m: 0,
                duration: const Duration(hours: 2, minutes: 59),
                ap: x.placeStart!)
            .include(changeTz(x.end!, x.placeStart!)));
  }

  static bool _isNightDuty(FtlDuty x) {
//200  459
    // print(
    //     "$x is Night duty: ${DTInterval.fromHm(apartir: changeTz(x.start!, x.placeStart!), h: 2, m: 0, duration: const Duration(hours: 2, minutes: 59)).include(x.end!)}");
    return (x.type != FtlDutyType.other) &&
        ((DTInterval.fromHm(
                    apartir: x.start!.toUtc(),
                    h: 2,
                    m: 0,
                    duration: const Duration(hours: 2, minutes: 59),
                    ap: x.placeStart!))
                .isOverlap(x.interval.toUtc()) ||
            (DTInterval.fromHm(
                    apartir: x.start!.subtract(days: 1).toUtc(),
                    h: 2,
                    m: 0,
                    duration: const Duration(hours: 2, minutes: 59),
                    ap: x.placeStart!))
                .isOverlap(x.interval.toUtc()));
  }

  Duration tzDiff(String iata1, String iata2, DateTime sourceDateTime) {
    // print(Airports.instance.find(iata1)!.timezoneid);
    // print(Airports.instance.find(iata2)!.timezoneid);
    final iata1Location = tz.getLocation(Airports.find(iata1)!.timezoneid);
    final iata2Location = tz.getLocation(Airports.find(iata2)!.timezoneid);
    tz.TZDateTime iata1TZDateTime =
        tz.TZDateTime.from(sourceDateTime, iata1Location);
    tz.TZDateTime iata2TZDateTime =
        tz.TZDateTime.from(sourceDateTime, iata2Location);
    return Duration(
        minutes: (iata2TZDateTime.timeZoneOffset.inMinutes -
                iata1TZDateTime.timeZoneOffset.inMinutes)
            .abs());
  }

  String _acclimatized(
      {required Duration tzoffset, required Duration timeElapsed}) {
    if (tzoffset.inHours <= 2) {
      return "D";
    } else if (timeElapsed.inHours < 48) {
      return "B";
    } else if (tzoffset.inHours < 4 && timeElapsed.inHours >= 48) {
      return "D";
    } else if (tzoffset.inHours <= 6 && timeElapsed.inHours >= 72) {
      return "D";
    } else if (tzoffset.inHours <= 9 && timeElapsed.inHours >= 96) {
      return "D";
    } else if (tzoffset.inHours <= 12 && timeElapsed.inHours >= 120) {
      return "D";
    } else {
      return "X";
    }
  }

  static Jiffy changeTz(Jiffy jiffy, String iata) {
    return Jiffy.parseFromDateTime(tz.TZDateTime.from(jiffy.dateTime,
        tz.getLocation(Airports.find(iata)?.timezoneid ?? "UTC")));
  }

  static DTInterval changeTzDt(DTInterval dt, String iata) {
    return DTInterval(changeTz(dt.start, iata), changeTz(dt.end, iata));
  }

  Duration _fdpMax(
      {required reftime,
      String? iata,
      required int sectors,
      required Map<String, List<String>> tab}) {
    Jiffy reftjiffy = Jiffy.now();
    String reft = "";

    if (reftime is Jiffy && iata != null) {
      reftjiffy = reftime;
      reftjiffy = changeTz(reftjiffy, iata);
      reft = reftjiffy.format(pattern: "HHmm");
    } else if (reftime is DateTime && iata != null) {
      reftjiffy = Jiffy.parseFromDateTime(reftime);
      reftjiffy = changeTz(reftjiffy, iata);
      reft = reftjiffy.format(pattern: "HHmm");
    } else if (reftime is String && reftime.length == 4) {
      reft = reftime;
    } else if (reftime is String && iata != null) {
      reftjiffy = Jiffy.parse(reftime);
      reftjiffy = changeTz(reftjiffy, iata);

      reft = reftjiffy.format(pattern: "HHmm");
    } else {
      throw ("_fdpMax type of reftime unrecognized!!!");
    }
    // print("$iata  $reft ${reftjiffy.toUtc().Hm}");
    for (String key in tab.keys) {
      List<String> intervalle = key.split("-");
      int cmph1 = reft.compareTo(intervalle[0]);
      int cmph2 = reft.compareTo(intervalle[1]);

      bool inint = (intervalle[0].compareTo(intervalle[1]) < 0)
          ? (cmph1 >= 0 && cmph2 <= 0)
          : ((cmph1 >= 0 || cmph2 <= 0));
      if (inint) {
        final List<String>? lres = tab[key];
        final int nrow = ((sectors == 1) ? 2 : sectors) - 2;
        final String? res =
            (lres != null && nrow < lres.length) ? lres[nrow] : null;
        if (res != null && res != "Not allowed") {
          return Duration(
              hours: int.parse(res.split(".")[0]),
              minutes: int.parse(res.split(".")[1]));
        }
      }
    }
    return Duration.zero;
  }

  Duration fdpMaxBasic(
          {required reftime, String? iata, required int sectors}) =>
      _fdpMax(reftime: reftime, iata: iata, sectors: sectors, tab: _fdpMaxTab);

  Duration fdpMaxExtBasic(
          {required reftime, String? iata, required int sectors}) =>
      _fdpMax(
          reftime: reftime, iata: iata, sectors: sectors, tab: _fdpMaxExtTab);

  Duration fdpMaxUnk({required reftime, String? iata, required int sectors}) =>
      _fdpMax(reftime: reftime, iata: iata, sectors: sectors, tab: _fdpUnkTab);

  Duration fdpMaxUnkFrms(
          {required reftime, String? iata, required int sectors}) =>
      _fdpMax(
          reftime: reftime, iata: iata, sectors: sectors, tab: _fdpUnkFrmsTab);

//acclim

  final Map<String, List<String>> _fdpMaxTab = {
    "0600-1329": [
      "13.00",
      "12.30",
      "12.00",
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
    ],
    "1330-1359": [
      "12.45",
      "12.15",
      "11.45",
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
    ],
    "1400-1429": [
      "12.30",
      "12.00",
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
    ],
    "1430-1459": [
      "12.15",
      "11.45",
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
      "09.00",
    ],
    "1500-1529": [
      "12.00",
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
      "09.00",
    ],
    "1530-1559": [
      "11.45",
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
      "09.00",
      "09.00",
    ],
    "1600-1629": [
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
      "09.00",
      "09.00",
    ],
    "1630-1659": [
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
      "09.00",
      "09.00",
      "09.00",
    ],
    "1700-0459": [
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
      "09.00",
      "09.00",
      "09.00",
    ],
    "0500-0514": [
      "12.00",
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
      "09.00",
    ],
    "0515-0529": [
      "12.15",
      "11.45",
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
      "09.00",
    ],
    "0530-0544": [
      "12.30",
      "12.00",
      "11.30",
      "11.00",
      "10.30",
      "10.00",
      "09.30",
      "09.00",
      "09.00",
    ],
    "0545-0559": [
      "12.45",
      "12.15",
      "11.45",
      "11.15",
      "10.45",
      "10.15",
      "09.45",
      "09.15",
      "09.00",
    ],
  };
  final Map<String, List<String>> _fdpMaxExtTab = {
    "0600-0614": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0615-0629": [
      "13.15",
      "12.45",
      "12.15",
      "11.45",
    ],
    "0630-0644": [
      "13.30",
      "13.00",
      "12.30",
      "12.00",
    ],
    "0645-0659": [
      "13.45",
      "13.15",
      "12.45",
      "12.15",
    ],
    "0700-1329": [
      "14.00",
      "13.30",
      "13.00",
      "12.30",
    ],
    "1330-1359": [
      "13.45",
      "13.15",
      "12.45",
      "Not allowed",
    ],
    "1400-1429": [
      "13.30",
      "13.00",
      "12.30",
      "Not allowed",
    ],
    "1430-1459": [
      "13.15",
      "12.45",
      "12.15",
      "Not allowed",
    ],
    "1500-1529": [
      "13.00",
      "12.30",
      "12.00",
      "Not allowed",
    ],
    "1530-1559": [
      "12.45",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1600-1629": [
      "12.30",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1630-1659": [
      "12.15",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1700-1729": [
      "12.00",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1730-1759": [
      "11.45",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1800-1829": [
      "11.30",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1830-1859": [
      "11.15",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "1900-0359": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0400-0414": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0415-0429": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0430-0444": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0445-0459": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0500-0514": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0515-0529": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0530-0544": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ],
    "0545-0559": [
      "Not allowed",
      "Not allowed",
      "Not allowed",
      "Not allowed",
    ]
  };
  final Map<String, List<String>> _fdpUnkTab = {
    "0000-2359": ["11.00", "10:30", "10.00", "09.30", "09.00", "09.00", "09.00"]
  };
  final Map<String, List<String>> _fdpUnkFrmsTab = {
    "0000-2359": ["12.00", "11.30", "11.00", "10.30", "10.00", "09.30", "09.00"]
  };
}

class FtlDuty {
  FtlDuty({this.start, this.end, this.placeStart, this.placeEnd, this.type});
  @override
  String toString() {
    return "${start?.yMEd} ${start?.Hm} >>> ${end?.Hm} : ${fdpLength.inMinutes > 0 ? "FDP" : "DUTY"} ${dutiesDetailsIndex.length}leg(s)";
  }

  Jiffy? start;
  Jiffy? end;
  DTInterval get interval => DTInterval(start!, end!);
  String? placeStart;
  String? placeEnd;
  FtlDutyType? type;
  List<int> dutiesDetailsIndex = [];
  Jiffy? fdpStart;
  Jiffy? fdpEnd;
  int sectors = 0;
  Duration? fdpMax;
  Duration? fdpExtMax;
  //Duration? fdpIfRExtMax;//if inflight rest rest

  bool fdpExt = false;
  List<FtlLegal> legals = [];

  Duration get dutyLength => (start != null && end != null)
      ? end!.dateTime.difference(start!.dateTime)
      : Duration.zero;
  Duration get fdpLength => (fdpStart != null && fdpEnd != null)
      ? fdpEnd!.dateTime.difference(fdpStart!.dateTime)
      : Duration.zero;

  // Duration? get rest => (dutyLength.max(Ftl.minimumrest(placeEnd)));
  Jiffy? restends;

  String? acclim;
  String? reftime;

  FtlDutyTotal? dutyTotal;

  //Jiffy? report;
  Jiffy? reportdelay1;
  Jiffy? notification1;
  Jiffy? reportdelay2;
  Jiffy? notification2;
  bool earlyStart = false;
  bool lateFinish = false;
  bool nightDuty = false;
}

class FtlLegal {
  FtlLegal(
      {required this.legalCause,
      required this.legalCauseMsg,
      this.legalIndex,
      this.condition});
  FtlLegalCause legalCause;
  String legalCauseMsg;
  int? legalIndex;
  String? condition;
}

enum FtlDutyType { duty, fdp, other }

class FtlDutyDetails {
  FtlDutyDetails({
    this.start,
    this.end,
    this.placeStart,
    this.placeEnd,
    this.type,
    this.label,
  });

  Jiffy? start;
  Jiffy? end;
  DTInterval get interval => DTInterval(start!, end!);

  Duration get duration => end!.dateTime.difference(start!.dateTime);
  String? placeStart;
  String? placeEnd;
  FtlDutyDetailsType? type;
  String get typeString {
    switch (type) {
      case FtlDutyDetailsType.flight:
        return "FLT";
      case FtlDutyDetailsType.dhflight:
        return "DH-FLT";
      case FtlDutyDetailsType.dhlimo:
        return "DH-LIMO";
      case FtlDutyDetailsType.sim:
        return "SIM";
      case FtlDutyDetailsType.standby:
        return "STBY";
      case FtlDutyDetailsType.reserve:
        return "RSRV";
      case FtlDutyDetailsType.ground:
        return "GRND";

      default:
        return "UNK";
    }
  }

  String? label;
}

enum FtlDutyDetailsType {
  preflight,
  flight,
  postflight,
  dhflight,
  dhlimo,
  ground,
  standby,
  reserve,
  sim,
//  training,
//  other
}

class FtlDutyTotal {
  FtlDutyTotal(
      {required this.date, required this.dutyLength, required this.fltLength});
  Jiffy date;
  Duration dutyLength;
  Duration fltLength;
  Duration? dutylast7;
  Duration? dutylast14;
  Duration? dutylast28;
  Duration? fltlast28;
  Duration? fltyear;
  Duration? fltlast12;
}

enum FtlLegalCause {
  dutylast7,
  dutylast14,
  dutylast28,
  fltlast28,
  fltyear,
  fltlast12,
  fdpmax,
  restfdp,
  restrecurrent,
  disruptive
  //check duty length && flt length

  //check fdp leength

  //check rest before fdp
  //check recurrent rest
  //check disruptive sched
}
