import 'dart:collection';

import 'package:aitrainer_app/main.dart';
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer_training_plan.dart';
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/model/training_plan.dart';
import 'package:aitrainer_app/model/training_plan_detail.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/customer_repository.dart';
import 'package:aitrainer_app/repository/mautic_repository.dart';
import 'package:aitrainer_app/repository/training_plan_repository.dart';
import 'package:aitrainer_app/service/exercise_service.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:aitrainer_app/widgets/exercise_save.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

part 'training_plan_event.dart';
part 'training_plan_state.dart';

class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> with Common {
  final TrainingPlanRepository trainingPlanRepository;
  final MenuBloc menuBloc;
  TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial()) {
    on<TrainingPlanCreateException>(_onCreateException);

    on<TrainingPlanActivate>(_onActivate);
    on<TrainingPlanWeightChangeRecalculate>(_onWeightChangeRecalculate);
    on<TrainingPlanSaveExercise>(_onSaveExercise);
    on<TrainingPlanAlternateChange>(_onChangeAlternate);
    on<TrainingPlanSkipExercise>(_onSkipExercise);
    on<TrainingPlanSkipEntireExercise>(_onSkipEntireExercise);
    on<TrainingPlanFinishDropset>(_onFinishDropSet);
    on<TrainingPlanFinishDay>(_onFinishDay);
    on<TrainingPlanChangeCancel>(_onChangeCancel);

    on<TrainingPlanAddLoad>(_onCustomLoad);
    on<TrainingPlanDeleteExerciseType>(_onDeleteExerciseType);
    on<TrainingPlanWeightChange>(_onCustomWeightChange);
    on<TrainingPlanSetChange>(_onCustomPlanSetChange);
    on<TrainingPlanRepeatsChange>(_onCustomPlanRepeatChange);
    on<TrainingPlanAddExerciseType>(_onCustomPlanAddExerciseType);
    on<TrainingPlanGoToRestart>(_onRestarting);

    // on<TrainingPlanError>(_onTrainingPlanError);
  }

  CustomerTrainingPlan? _myPlan;
  CustomerTrainingPlanDetails? _myDetail;
  CustomerTrainingPlanDetails? _backupDetail;

  TrainingPlan? _myTrainingPlan;
  final List<String> trainingPlanDayNames = [];

  bool started = false;
  final List<String> dayNames = [];
  bool restarting = false;
  bool celebrating = false;
  int activeDayIndex = 0;

  double base1RM = 0;

  CustomerTrainingPlan? getMyPlan() => this._myPlan;
  setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan;

  TrainingPlan? getMyTrainingPlan() => this._myTrainingPlan;
  setMyTrainingPlan(TrainingPlan? myTrainingPlan) => this._myTrainingPlan = myTrainingPlan;

  CustomerTrainingPlanDetails? getMyDetail() => this._myDetail;
  setMyDetail(CustomerTrainingPlanDetails? value) => this._myDetail = value;

  HashMap<int, List<CustomerTrainingPlanDetails>> _alternatives = HashMap();
  get alternatives => this._alternatives;

  /// _onActivate
  /// activating the training plan, calculating the weights to the given repeats
  /// from previous exercises
  ///
  /// event: TrainingPlanActivate
  void _onActivate(TrainingPlanActivate event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    _myPlan = trainingPlanRepository.activateTrainingPlan(event.trainingPlanId);
    _myPlan!.type = CustomerTrainingPlanType.template;
    this.createAlternatives();

    menuBloc.menuTreeRepository.sortedTree.forEach((name, list) {
      final List<WorkoutMenuTree> menuList = list as List<WorkoutMenuTree>;
      menuList.forEach((element) {
        element.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.none;
      });
    });
    this.activateDays();
    Cache().myTrainingPlan = _myPlan;
    await Cache().saveMyTrainingPlan();
    Track().track(TrackingEvent.training_plan_start, eventValue: event.trainingPlanId.toString());
    emit(TrainingPlanFinished());
  }

  void _onWeightChangeRecalculate(TrainingPlanWeightChangeRecalculate event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanExerciseLoading());

    if (event.detail.baseOneRepMax > 0 && event.detail.repeats != -1) {
      if (_myTrainingPlan == null) {
        setTrainingPlanFromCache();
      }
      final int originalRepeats = trainingPlanRepository.getOriginalRepeats(_myTrainingPlan!.trainingPlanId, event.detail);
      event.detail.repeats = Common.calculateQuantityByChangedWeight(event.detail.baseOneRepMax, event.weight, originalRepeats.toDouble());

      print("Base1RM ${event.detail.baseOneRepMax} repeats: ${event.detail.repeats}");
      if (event.detail.repeats! < 1) {
        event.detail.repeats = 1;
        event.detail.weight = trainingPlanRepository.getOriginalWeight(_myTrainingPlan!.trainingPlanId, event.detail);
      }
      ExerciseSaveStream().repeats = event.detail.repeats!;
      ExerciseSaveStream().getStreamController().add(true);
    }
    event.detail.weight = event.weight;

    ///yield TrainingPlanExerciseReady();
    emit(TrainingPlanReady());
  }

  /// _onSaveExercise
  /// - save the executed exercise to the DB
  /// - recalculate, if necessary the next weight to the give repeats
  /// - track this event
  /// - send the data to Mautic
  /// if [DropSet], the just set the state of the detail to finish
  ///
  /// event: TrainingPlanWeightChange
  void _onSaveExercise(TrainingPlanSaveExercise event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    try {
      if (event.detail.repeats == -1) {
        emit(TrainingPlanReady());
        throw Exception("Please type your repeats!");
      }
      print("SAVE for ExerciseTypeID: ${event.detail.exerciseTypeId} weight: ${event.detail.weight}");
      if (event.detail.weight == -3) {
        print("DropSet");
        event.detail.state = ExercisePlanDetailState.finished;
        emit(TrainingPlanReady());
      } else {
        final Exercise exercise = Exercise();
        exercise.customerId = Cache().userLoggedIn!.customerId!;
        exercise.exerciseTypeId = event.detail.exerciseTypeId;
        exercise.quantity = event.detail.repeats!.toDouble();
        exercise.unit = event.detail.exerciseType!.unit;
        exercise.unitQuantity = event.detail.weight;
        exercise.dateAdd = DateTime.now();
        event.detail.exercises.add(exercise);
        if (this.isAllDetailsSameExerciseFinished(event.detail)) {
          event.detail.state = ExercisePlanDetailState.finished;
        }

        // recalculate the weight to the original planned repeats for the next details
        this.recalculateNextDetails(event.detail);
        exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;

        // save Exercise
        Exercise savedExercise = await ExerciseApi().addExercise(exercise);
        Cache().addExercise(savedExercise);

        Cache().myTrainingPlan = _myPlan;
        await Cache().saveMyTrainingPlan();
      }

      if (kReleaseMode) {
        CustomerRepository customerRepository = CustomerRepository();
        customerRepository.customer = Cache().userLoggedIn;
        MauticRepository mauticRepository = MauticRepository(customerRepository: customerRepository);
        await mauticRepository.sendMauticExercise();
      }
      Track().track(TrackingEvent.training_plan_execute, eventValue: event.detail.exerciseType!.name);

      if (isDayDone()) {
        this.add(TrainingPlanFinishDay());
      } else {
        emit(TrainingPlanReady());
      }
    } on Exception catch (e) {
      emit(TrainingPlanError(message: e.toString()));
    }
  }

  void recalculateNextDetails(CustomerTrainingPlanDetails eventDetail) {
    int id = 0;
    bool recalculate = false;

    int baseCustomerTrainingPlanDetailsId = 0;
    if (eventDetail.exerciseType!.unitQuantity != null && eventDetail.weight! > 0) {
      for (var nextDetail in _myPlan!.details) {
        if (nextDetail.exerciseTypeId == eventDetail.exerciseTypeId) {
          if (id == 0 && nextDetail.customerTrainingPlanDetailsId == eventDetail.customerTrainingPlanDetailsId) {
            id = nextDetail.customerTrainingPlanDetailsId!;
            recalculate = true;
          }
          if (id == 0) {
            // we are after the first exercise
            baseCustomerTrainingPlanDetailsId = nextDetail.customerTrainingPlanDetailsId!;
          }
          double weightFromPlan = trainingPlanRepository.getOriginalWeight(this.getMyPlan()!.trainingPlanId!, nextDetail);
          print("NextDetail detail: $nextDetail *** PlanWeight: $weightFromPlan");
          if (nextDetail.weight == -2 && nextDetail.customerTrainingPlanDetailsId != eventDetail.customerTrainingPlanDetailsId) {
            print("Nr 1. - recalculating -2 ${eventDetail.customerTrainingPlanDetailsId}");
            trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, eventDetail, nextDetail);
            nextDetail.baseOneRepMax = calculate1RM(nextDetail.weight!, nextDetail.repeats!.toDouble());
          } /* else if (weightFromPlan == -1 && nextDetail.set! > 1 && nextDetail.exercises.length == 1) {
                print("Nr 2. recalculating -1 ${event.detail.customerTrainingPlanDetailsId}");
                nextDetail = trainingPlanRepository.recalculateDetailFixRepeats(_myPlan!.trainingPlanId!, nextDetail);
                nextDetail.baseOneRepMax = Common.calculate1RM(nextDetail.weight!, nextDetail.repeats!.toDouble());
              }  */
          else if (nextDetail.weight == -1 && nextDetail.set! == 1) {
            print("Nr 3. recalculating -1, set 1 ${eventDetail.customerTrainingPlanDetailsId}");
            nextDetail = trainingPlanRepository.recalculateDetailFixRepeatsSet1(_myPlan!.trainingPlanId!, nextDetail, eventDetail);
            nextDetail.baseOneRepMax = calculate1RM(nextDetail.weight!, nextDetail.repeats!.toDouble());
          } else if (eventDetail.set! == 1 &&
              (weightFromPlan == -2 || weightFromPlan == -1) &&
              nextDetail.customerTrainingPlanDetailsId! == id + 1 &&
              recalculate) {
            print("Nr 4. recalculating after the first exercise ${eventDetail.customerTrainingPlanDetailsId}");
            nextDetail = trainingPlanRepository.recalculateDetailFixRepeatsSet1(_myPlan!.trainingPlanId!, nextDetail, eventDetail);
            nextDetail.baseOneRepMax = calculate1RM(nextDetail.weight!, nextDetail.repeats!.toDouble());
          } else if (eventDetail.set! == 1 &&
              (weightFromPlan == -2 || weightFromPlan == -1) &&
              nextDetail.customerTrainingPlanDetailsId! > id + 1 &&
              recalculate) {
            print("Nr 5. recalculating after the second exercise ${eventDetail.customerTrainingPlanDetailsId}");
            nextDetail = trainingPlanRepository.recalculateDetailFixRepeatsSet1(_myPlan!.trainingPlanId!, nextDetail, eventDetail);
            nextDetail.baseOneRepMax = calculate1RM(nextDetail.weight!, nextDetail.repeats!.toDouble());
          } else if (id != 0) {
            // calculate weight and repeat based on the first baseOneRepMax
            if (baseCustomerTrainingPlanDetailsId != 0) {
              CustomerTrainingPlanDetails? firstDetail = trainingPlanRepository.getDetailById(_myPlan, baseCustomerTrainingPlanDetailsId);
              if (firstDetail != null) {
                print("Nr 6. recalculating based on the first exercise $nextDetail");
                nextDetail = trainingPlanRepository.recalculateDetailFixRepeatsSet1(_myPlan!.trainingPlanId!, nextDetail, firstDetail);
              }
            }
          }
        }
      }
    }
  }

  void _onChangeAlternate(TrainingPlanAlternateChange event, Emitter<TrainingPlanState> emit) {
    emit(TrainingPlanLoading());
    if (_myPlan == null || _myPlan!.details.length == 0) {
      emit(TrainingPlanReady());
      return;
    }

    int index = 0;

    for (var planDetail in _myPlan!.details) {
      int key = planDetail.customerTrainingPlanDetailsId!;
      if (_alternatives[key]!.length >= event.index + 1 && _alternatives[key]![event.index].exerciseTypeId == event.detail.exerciseTypeId) {
        CustomerTrainingPlanDetails alternative = _alternatives[key]![event.index];
        _myPlan!.details[index] = alternative;
        changeExerciseInDayNames(planDetail.exerciseTypeId!, planDetail.day!, alternative);
        print("Changed alternative $alternative");
      }
      index++;
    }

    emit(TrainingPlanReady());
  }

  void changeExerciseInDayNames(int exerciseTypeId, String dayName, CustomerTrainingPlanDetails alternative) {
    if (_myPlan != null && _myPlan!.days[dayName] != null) {
      int index = 0;
      for (var detail in _myPlan!.days[dayName]!) {
        if (detail.exerciseTypeId == exerciseTypeId) {
          _myPlan!.days[dayName]![index] = alternative;
        }
        index++;
      }
    }
  }

  void _onSkipExercise(TrainingPlanSkipExercise event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    event.detail.state = ExercisePlanDetailState.skipped;
    this.recalculateNextDetails(event.detail);
    print(" * Skipping ${event.detail.exerciseTypeId}");
    Cache().myTrainingPlan = _myPlan;
    await Cache().saveMyTrainingPlan();
    if (isDayDone()) {
      this.add(TrainingPlanFinishDay());
    } else {
      emit(TrainingPlanReady());
    }
  }

  void _onSkipEntireExercise(TrainingPlanSkipEntireExercise event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    List<CustomerTrainingPlanDetails> list = getAllDetailsSameExercise(event.detail);
    list.forEach((element) {
      if (!element.state.equalsTo(ExercisePlanDetailState.finished)) {
        element.state = ExercisePlanDetailState.skipped;
      }
      Cache().myTrainingPlan = _myPlan;
    });
    await Cache().saveMyTrainingPlan();
    if (isDayDone()) {
      this.add(TrainingPlanFinishDay());
    } else {
      emit(TrainingPlanReady());
    }
  }

  void _onFinishDropSet(TrainingPlanFinishDropset event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    event.detail.state = ExercisePlanDetailState.finished;
    emit(TrainingPlanReady());
  }

  void _onFinishDay(TrainingPlanFinishDay event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    celebrating = true;
    Track().track(TrackingEvent.training_plan_finished);
    emit(TrainingPlanDayFinished());
  }

  void _onChangeCancel(TrainingPlanChangeCancel event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    print("Backup $_backupDetail");
    if (_backupDetail != null) {
      event.detail.repeats = _backupDetail!.repeats;
      event.detail.weight = _backupDetail!.weight;
    }
    emit(TrainingPlanReady());
  }

  void _onCustomLoad(TrainingPlanAddLoad event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    addNewPlan();

    _myDetail = CustomerTrainingPlanDetails();
    _myDetail!.exerciseType = event.exerciseType;
    _myDetail!.exerciseTypeId = event.exerciseType.exerciseTypeId;
    _myDetail!.repeats = 12;
    _myDetail!.weight = 30;
    _myDetail!.set = 3;
    _myDetail!.parallel = false;
    _myDetail!.day = "";
    _myDetail!.restingTime = 2;
    _myDetail!.state = ExercisePlanDetailState.start;

    if (_myDetail!.exerciseType!.unitQuantityUnit != null) {
      _myDetail = trainingPlanRepository.getCalculatedWeightRepeats(event.exerciseType.exerciseTypeId, _myDetail!);
    } else {
      _myDetail!.weight = 0;
    }
    Track().track(TrackingEvent.training_plan_custom, eventValue: event.exerciseType.name);
    emit(TrainingPlanReady());
  }

  void _onDeleteExerciseType(TrainingPlanDeleteExerciseType event, Emitter<TrainingPlanState> emit) async {
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      throw Exception("No MyPlan");
    }
    emit(TrainingPlanLoading());
    CustomerTrainingPlanDetails? remove;
    for (var detail in _myPlan!.details) {
      if (event.exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        remove = detail;
        break;
      }
    }
    if (remove != null) {
      _myPlan!.details.remove(remove);
    }
    event.exerciseType.trainingPlanState = ExerciseTypeTrainingPlanState.none;
    emit(TrainingPlanReady());
  }

  /// _onWeightChange
  /// change the weight of the actual [CustomerTrainingPlanDetails] from the UI
  ///
  /// event: TrainingPlanWeightChange
  void _onCustomWeightChange(TrainingPlanWeightChange event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanExerciseLoading());
    event.detail.weight = event.weight;

    emit(TrainingPlanExerciseReady());
    emit(TrainingPlanReady());
  }

  void _onCustomPlanSetChange(TrainingPlanSetChange event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    event.detail.set = event.set;
    emit(TrainingPlanReady());
  }

  void _onCustomPlanRepeatChange(TrainingPlanRepeatsChange event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    event.detail.repeats = event.repeats;
    emit(TrainingPlanReady());
  }

  void _onCustomPlanAddExerciseType(TrainingPlanAddExerciseType event, Emitter<TrainingPlanState> emit) async {
    if (_myDetail == null) {
      throw Exception("Create new Detail");
    }
    emit(TrainingPlanLoading());
    _myDetail!.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.added;
    _myDetail!.customerTrainingPlanDetailsId = _myPlan!.details.length + 1;
    _myPlan!.details.add(this._myDetail!);
    emit(TrainingPlanReady());
  }

  void _onRestarting(TrainingPlanGoToRestart event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    restarting = true;
    emit(TrainingPlanDayReadyToRestart());
  }

  void _onCreateException(TrainingPlanCreateException event, Emitter<TrainingPlanState> emit) async {
    emit(TrainingPlanLoading());
    print("Bloc execption caught: ${event.message}");
    emit(TrainingPlanError(message: event.message));
  }

  /* ***************************************************
  * Helper Functions
  ******************************************************/

  void createAlternatives() {
    if (_myPlan == null || _myPlan!.details.length == 0 || _myPlan!.trainingPlanId == null) {
      return;
    }
    _alternatives.clear();
    TrainingPlanDetail? trainingPlanDetail;
    _myTrainingPlan = trainingPlanRepository.getTrainingPlanById(_myPlan!.trainingPlanId!);

    _myPlan!.details.forEach((detail) {
      if (detail.alternatives.length > 0) {
        detail.alternatives.forEach((alternative) {
          if (_myTrainingPlan != null) {
            for (var planDetail in _myTrainingPlan!.details!) {
              if (planDetail.exerciseTypeId == detail.exerciseTypeId!) {
                trainingPlanDetail = planDetail;
                CustomerTrainingPlanDetails alternativeDetail =
                    trainingPlanRepository.createAlternativeDetail(_myPlan!, detail, trainingPlanDetail!, alternative.exerciseTypeId);
                if (_alternatives[alternativeDetail.customerTrainingPlanDetailsId!] == null) {
                  _alternatives[alternativeDetail.customerTrainingPlanDetailsId!] = [];
                  _alternatives[detail.customerTrainingPlanDetailsId!]!.add(detail); // element zero
                }
                if (!existsAlternativeExercise(detail.customerTrainingPlanDetailsId!, alternativeDetail)) {
                  _alternatives[alternativeDetail.customerTrainingPlanDetailsId!]!.add(alternativeDetail);
                }
              }
            }
          } else {
            if (_alternatives[detail.customerTrainingPlanDetailsId!] == null) {
              _alternatives[detail.customerTrainingPlanDetailsId!] = [];
              _alternatives[detail.customerTrainingPlanDetailsId!]!.add(detail); // element zero
            }
          }
        });
      } else {
        if (_alternatives[detail.customerTrainingPlanDetailsId!] == null) {
          _alternatives[detail.customerTrainingPlanDetailsId!] = [];
        }
        _alternatives[detail.customerTrainingPlanDetailsId!]!.add(detail);
      }
      //print(
      //  "Created alternaties for ${detail.exerciseTypeId} and repeat ${detail.repeats} -- ${_alternatives[detail.customerTrainingPlanDetailsId!]}");
    });
  }

  bool existsAlternativeExercise(int customerTrainingPlanDetailsId, CustomerTrainingPlanDetails alternative) {
    bool exists = false;
    for (var actual in _alternatives[customerTrainingPlanDetailsId]!) {
      if (actual.exerciseTypeId == alternative.exerciseTypeId) {
        exists = true;
        break;
      }
    }
    return exists;
  }

  void backupDetail(CustomerTrainingPlanDetails detail) {
    if (_backupDetail == null) {
      _backupDetail = CustomerTrainingPlanDetails();
    }
    _backupDetail!.copy(detail);
  }

  void setTrainingPlanFromCache() {
    if (_myPlan == null) {
      setMyPlan(Cache().myTrainingPlan);
    }
    Cache().getTrainingPlans()!.forEach((element) {
      if (_myPlan!.trainingPlanId == element.trainingPlanId) {
        _myTrainingPlan = element;
      }
    });
  }

  void addNewPlan() {
    if (Cache().userLoggedIn == null) {
      throw Exception("Please log in");
    }
    if (_myPlan == null) {
      _myPlan = CustomerTrainingPlan();
    } else {
      if (!_myPlan!.type.equalsTo(CustomerTrainingPlanType.custom)) {
        _myPlan!.details.clear();
      }
    }
    _myPlan!.trainingPlanId = 0;
    _myPlan!.name = "Custom";
    _myPlan!.dateAdd = DateTime.now();
    _myPlan!.customerId = Cache().userLoggedIn == null ? Cache().userLoggedIn!.customerId : 0;

    _myPlan!.type = CustomerTrainingPlanType.custom;

    restart();

    print("New custom plan: $_myPlan");
  }

  void restart() {
    if (_myPlan == null) {
      return;
    }
    for (var day in dayNames) {
      _myPlan!.days[day]!.clear();
    }
    _myPlan!.details.forEach((element) {
      element.state = ExercisePlanDetailState.start;
      element.exercises.clear();
    });
    dayNames.clear();
    Cache().myTrainingPlan = _myPlan;
    Cache().saveMyTrainingPlan();
    restarting = false;
    print("Restarting finished");
  }

  void activateDays() {
    if (_myPlan == null) {
      return;
    }

    if (isDone100Percent()) {
      this.add(TrainingPlanGoToRestart());
      print("---------------- TrainingPlanGoToRestart");
    }
    dayNames.clear();
    _myPlan!.days.clear();
    String dayName = ".";
    String previousDay = ".";
    _myPlan!.details.forEach((element) {
      if (element.day != null && element.day != dayName) {
        dayNames.add(element.day!);
        dayName = element.day!;
        if (previousDay != ".") {
          this.addExtraExerciseType("Stretching", previousDay);
        }
      }
      if (_myPlan!.days[dayName] == null) {
        if (dayName == ".") {
          dayName = "";
        }
        _myPlan!.days[dayName] = [];

        this.addExtraExerciseType("Warming Up", dayName);
        previousDay = dayName;
      }
      _myPlan!.days[dayName]!.add(element);
    });
    this.addExtraExerciseType("Stretching", previousDay);

    if (dayNames.length == 0) {
      dayName = "";
      dayNames.add(dayName);
      _myPlan!.days[dayName] = [];
      _myPlan!.days[dayName]!.addAll(_myPlan!.details);
    }
    getActiveDayIndex();

    createAlternatives();
  }

  void activateTrainingPlanDays() {
    if (_myTrainingPlan == null || _myTrainingPlan!.details == null) {
      return;
    }

    trainingPlanDayNames.clear();

    String dayName = ".";
    _myTrainingPlan!.details!.forEach((element) {
      if (element.day != null && element.day != dayName) {
        trainingPlanDayNames.add(element.day!);
        dayName = element.day!;
      }
    });

    if (trainingPlanDayNames.length == 0) {
      dayName = "";
      trainingPlanDayNames.add(dayName);
    }
  }

  List<TrainingPlanDetail> trainingPlanDetailSummary(TrainingPlan plan, String dayName) {
    List<TrainingPlanDetail> details = [];
    TrainingPlanDetail? prev;
    plan.details!.forEach((element) {
      if (prev == null || element.exerciseTypeId != prev!.exerciseTypeId) {
        if (element.day! == dayName) {
          element.summary = getSummary(element);
          details.add(element);
        }
        prev = element;
      }
    });
    return details;
  }

  List<TrainingPlanDetail> getAllTrainingPlanDetailsSameExercise(TrainingPlanDetail detail) {
    List<TrainingPlanDetail> list = [];
    getMyTrainingPlan()!.details!.forEach((element) {
      if (detail.exerciseTypeId == element.exerciseTypeId) {
        list.add(element);
      }
    });
    return list;
  }

  String getSummary(TrainingPlanDetail detail) {
    String summary = "";

    List<TrainingPlanDetail> details = getAllTrainingPlanDetailsSameExercise(detail);
    int index = 0;

    String quantities = "";

    details.forEach((element) {
      String delimiter = ", ";
      if (index == 0) {
        delimiter = "";
      }
      if (element.repeats == -1) {
        quantities += delimiter + " MAX ";
      } else {
        quantities += delimiter + "${element.repeats}";
      }

      index++;
    });

    //quantities += " / ? kg";

    summary = quantities;

    return summary;
  }

  void addExtraExerciseType(String name, String dayName) {
    if (Cache().getExerciseTypes() == null) {
      return;
    }
    for (var exerciseType in Cache().getExerciseTypes()!) {
      if (exerciseType.name == name) {
        CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
        detail.customerTrainingPlanDetailsId = 0;
        detail.trainingPlanDetailsId = 0;
        detail.exerciseTypeId = exerciseType.exerciseTypeId;
        detail.repeats = 1;
        detail.set = 1;
        detail.day = "";
        detail.parallel = false;
        detail.restingTime = 0;
        detail.exerciseType = exerciseType;
        detail.state = ExercisePlanDetailState.extra;
        if (_myPlan!.days[dayName] == null) {
          _myPlan!.days[dayName] = [];
        }
        _myPlan!.days[dayName]!.add(detail);
        break;
      }
    }
  }

  CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) {
    CustomerTrainingPlanDetails? detail;
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return null;
    }

    for (final det in this._myPlan!.details) {
      if (det.customerTrainingPlanDetailsId == trainingPlanDetailsId) {
        detail = det;
        break;
      }
    }

    return detail;
  }

  int? getActualWorkoutTreeId(int exerciseTypeId) {
    final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
    if (workoutTree == null) {
      return null;
    }
    return workoutTree.id;
  }

  String getActualImageName(int exerciseTypeId) {
    if (exerciseTypeId <= 0) {
      return "";
    }
    final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
    if (workoutTree == null) {
      return "";
    }

    return workoutTree.imageName;
  }

  int getStep(CustomerTrainingPlanDetails detail) {
    List<CustomerTrainingPlanDetails> details = getAllDetailsSameExercise(detail);
    int step = 0;
    details.forEach((element) {
      if (element.exercises.length < element.set! && element.exercises.length > 0) {
        step = element.exercises.length;
      }
    });
    step++;
    //print("STEP: $step ");
    return step;
  }

  int getHighlightStep(CustomerTrainingPlanDetails detail) {
    List<CustomerTrainingPlanDetails> details = getAllDetailsSameExercise(detail);
    int step = 0;
    details.forEach((element) {
      //print("Highlight element $element");
      if (element.exercises.length >= element.set!) {
        step++;
      }
    });
    //print("Highlight step: $step ");
    return step;
  }

  CustomerTrainingPlanDetails? getNext() {
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return null;
    }

    CustomerTrainingPlanDetails? next;
    int minStep = 99;
    for (final detail in this._myPlan!.details) {
      if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
        final day = dayNames[this.activeDayIndex];
        if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) {
          next = detail;
          minStep = 1;
          break;
        } else {
          final int step = detail.exercises.length;
          if (step < minStep &&
              step < detail.set! &&
              !isAllDetailsSameExerciseFinished(detail) &&
              !detail.state.equalsTo(ExercisePlanDetailState.skipped) &&
              day == detail.day) {
            next = detail;
            minStep = step;
            //if (detail.parallel != true) {
            break;
            //}
          }
        }
      }
    }
    //print("Next detail $next");
    if (next != null) {
      //this.recalculateNextDetails(next);
    }
    return next;
  }

  bool isStarted() {
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return false;
    }

    if (_myPlan!.details[0].state == ExercisePlanDetailState.start) {
      return false;
    } else {
      return true;
    }
  }

  bool isDayDone() {
    bool isDone = true;
    final String day = dayNames[activeDayIndex];
    for (var detail in _myPlan!.days[day]!) {
      //print("daydone ${detail.state}");
      if (!detail.state.equalsTo(ExercisePlanDetailState.extra) &&
          !detail.state.equalsTo(ExercisePlanDetailState.finished) &&
          !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
        isDone = false;
      }
    }
    print("Is Day '$day' done: $isDone");
    return isDone;
  }

  double getOffset() {
    double offset = 5;
    if (_myPlan == null) {
      return offset;
    }
    int indexInProgress = 0;
    int indexInStart = 0;
    final String day = dayNames[this.activeDayIndex];
    CustomerTrainingPlanDetails? prev;
    for (var detail in _myPlan!.days[day]!) {
      //print("Offset detail $detail");
      if (detail.state == ExercisePlanDetailState.inProgress || detail.state == ExercisePlanDetailState.start) {
        prev = detail;
        break;
      }

      if (prev != null && prev.exerciseTypeId != detail.exerciseTypeId || detail.state == ExercisePlanDetailState.extra) {
        //print(" --- offset + 1");
        indexInStart++;
        indexInProgress++;
      }
      prev = detail;
    }
    double index = indexInStart > indexInProgress ? indexInStart.toDouble() : indexInProgress.toDouble();
    offset = 325 * index;
    print("Offset: $offset day: $day ($indexInStart, $indexInProgress)");
    return offset;
  }

  bool isDone100Percent() {
    bool done = true;
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return false;
    }
    _myPlan!.details.forEach((element) {
      if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
        done = false;
      }
    });
    return done;
  }

  int getActiveDayIndex() {
    activeDayIndex = 0;
    if (restarting) {
      return 0;
    }
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return 0;
    }

    if (dayNames.isEmpty || dayNames.length == 1) {
      activeDayIndex = 0;
      return 0;
    }

    for (var day in dayNames) {
      if (_myPlan!.days[day] == null) {
        throw Exception("Wrong activated day: $day does not exist");
      }
      bool isDone = true;
      _myPlan!.days[day]!.forEach((element) {
        if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
          isDone = false;
        }
      });
      if (!isDone) {
        break;
      }
      activeDayIndex++;
    }

    if (activeDayIndex >= dayNames.length) {
      activeDayIndex = 0;
      this.add(TrainingPlanGoToRestart());
      print("---------------- TrainingPlanGoToRestart");
    }
    print("ActiveDayIndex $activeDayIndex");
    return activeDayIndex;
  }

  String getCustomAddSummary() {
    String summary = "";
    if (_myDetail == null || _myDetail!.set == null || _myDetail!.repeats == null) {
      return summary;
    }
    summary = getMyDetail()!.set!.toStringAsFixed(0) + " x " + getMyDetail()!.repeats!.toStringAsFixed(0);
    return summary;
  }

  String getExerciseName(Locale locale) {
    String exerciseName = "";
    if (_myDetail == null || _myDetail!.exerciseType == null) {
      return exerciseName;
    }
    exerciseName = AppLanguage().appLocal == Locale("en") ? getMyDetail()!.exerciseType!.name : getMyDetail()!.exerciseType!.nameTranslation;
    return exerciseName;
  }

  String getWeightByExerciseType(ExerciseType exerciseType) {
    double weight = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return weight.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        weight = detail.weight!;
        break;
      }
    }

    int decimal = weight % weight.round() == 0 ? 0 : 1;
    return weight.toStringAsFixed(decimal);
  }

  String getSetByExerciseType(ExerciseType exerciseType) {
    int value = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return value.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        value = detail.set!;
        break;
      }
    }

    return value.toStringAsFixed(0);
  }

  String getRepeatsByExerciseType(ExerciseType exerciseType) {
    int value = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return value.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        value = detail.repeats!;
        break;
      }
    }

    return value.toStringAsFixed(0);
  }

  bool existsAddedExerciseTypeInTree(String name) {
    bool exists = false;
    final List<WorkoutMenuTree>? listWorkoutTree = menuBloc.menuTreeRepository.sortedTree[name];
    if (listWorkoutTree != null) {
      listWorkoutTree.forEach((element) {
        if (element.exerciseType!.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) {
          exists = true;
        }
      });
    }
    return exists;
  }

  List<CustomerTrainingPlanDetails> getAllDetailsSameExercise(CustomerTrainingPlanDetails detail) {
    List<CustomerTrainingPlanDetails> list = [];
    getMyPlan()!.details.forEach((element) {
      if (detail.exerciseTypeId == element.exerciseTypeId) {
        list.add(element);
      }
    });
    return list;
  }

  bool isAllDetailsSameExerciseFinished(CustomerTrainingPlanDetails detail) {
    bool allFinished = true;
    List<CustomerTrainingPlanDetails> list = getAllDetailsSameExercise(detail);
    //print("SAME count: ${list.length}");
    for (var listDetail in list) {
      //print("SAME $listDetail");
      if (listDetail.exercises.length >= listDetail.set!) {
        listDetail.state = ExercisePlanDetailState.finished;
      }
      allFinished = allFinished && (listDetail.exercises.length >= listDetail.set! || listDetail.state.equalsTo(ExercisePlanDetailState.skipped));
    }
    //print("All finished: $allFinished for ${detail.exerciseTypeId}");
    return allFinished;
  }
}
