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

import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/repository/workout_tree_repository.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/calculate.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/group_data.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:intl/intl.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:fl_chart/fl_chart.dart';

part 'development_by_muscle_event.dart';

part 'development_by_muscle_state.dart';

class DiagramType {
  static String sumMass = "sumMass";
  static String oneRepMax = "oneRepMax";
  static String percent = "percent";
}

/*
 =========== GROUPDATE CLASS
 */

class GroupDate extends GroupData with Calculate, Common {
  final List<Exercise> inputList;
  final List<Exercise> outputList;

  String? _origDatePart;
  late int _origExerciseTypeId;
  late Exercise _origExercise;

  late double _sumQuantity;
  late double _maxQuantity;
  late int _countExercises;

  late String diagramType;
  late String dateRate;

  GroupDate({required this.inputList, required this.outputList});

  double getQuantityByDate(Exercise exercise) {
    double sum = 0;
    if (this.diagramType == DiagramType.sumMass) {
      if (exercise.unitQuantity != null) {
        sum = exercise.quantity! * exercise.unitQuantity!;
      } else {
        sum = exercise.quantity!;
      }
    } else if (this.diagramType == DiagramType.oneRepMax || this.diagramType == DiagramType.percent) {
      if (exercise.unitQuantity != null) {
        sum = calculate1RM(exercise.quantity!, exercise.unitQuantity!);
      } else {
        sum = exercise.quantity!;
      }
    }
    return sum;
  }

  @override
  void addTempData(Exercise exercise) {
    double newQuantity = getQuantityByDate(exercise);
    _sumQuantity = _sumQuantity + newQuantity;
    if (_maxQuantity < newQuantity) {
      _maxQuantity = newQuantity;
    }
    _countExercises = _countExercises + 1;
    _origDatePart = getDatePart(exercise.dateAdd!, dateRate);
    _origExerciseTypeId = exercise.exerciseTypeId!;
    _origExercise = exercise;
  }

  @override
  bool checkNewType(Exercise exercise) {
    String exerciseDatePart = getDatePart(exercise.dateAdd!, dateRate);
    return _origDatePart == null || _origDatePart != exerciseDatePart || _origExerciseTypeId != exercise.exerciseTypeId;
  }

  @override
  void iteration() {
    this.resetTemp();
    Exercise? tempExercise;
    inputList.forEach((element) {
      tempExercise = element;
      if (this.checkNewType(element)) {
        if (_origDatePart == null) {
          this.addTempData(element);
        } else {
          this.temp2Output(_origExercise);
          this.resetTemp();
          this.addTempData(element);
        }
      } else {
        this.addTempData(element);
      }
    });
    if (tempExercise != null) {
      this.temp2Output(tempExercise!);
    }
  }

  @override
  void temp2Output(Exercise exercise) {
    if (exercise.unitQuantity == null) {
      return;
    }
    Exercise newExercise = exercise.copy();
    newExercise.datePart = _origDatePart;
    if (this.diagramType == DiagramType.oneRepMax || this.diagramType == DiagramType.percent) {
      newExercise.calculated = _maxQuantity;
    } else {
      newExercise.calculated = _sumQuantity / _countExercises;
    }
    outputList.add(newExercise);
  }

  @override
  void resetTemp() {
    _countExercises = 0;
    _sumQuantity = 0;
    _maxQuantity = 0;
  }
}

/*
 =========== CHART DATA CLASS
 */
class GroupChart extends GroupData with Calculate {
  final List<dynamic> inputList;
  LinkedHashMap<int, ChartDataExtended> outputList = LinkedHashMap();

  int? _origExerciseTypeId;
  late double _minData = 999999999999;
  late double _maxData = 0;
  late List<BarChartGroupData> _chartData;
  late double _basePercent;

  String diagramType = DiagramType.sumMass;

  GroupChart({required this.inputList, required this.outputList});

  double getBasePercent(Exercise exercise) {
    if (exercise.unitQuantity != null) {
      this._basePercent = calculate1RM(exercise.quantity!, exercise.unitQuantity!);
    } else {
      this._basePercent = exercise.quantity!;
    }
    return this._basePercent;
  }

  double getDiagramValue(Exercise exercise) {
    double value = 0;

    if (diagramType == DiagramType.percent) {
      if (exercise.unitQuantity != null) {
        double oneRepMax = calculate1RM(exercise.quantity!, exercise.unitQuantity!);
        value = (oneRepMax / this._basePercent) * 100;
      } else {
        value = (exercise.quantity! / this._basePercent) * 100;
      }
    } else if (diagramType == DiagramType.oneRepMax || diagramType == DiagramType.sumMass) {
      value = exercise.calculated!;
    }
    return value;
  }

  @override
  void addTempData(Exercise exercise) {
    double diagramValue = this.getDiagramValue(exercise);

    if (_maxData < diagramValue) {
      _maxData = diagramValue;
    }
    if (_minData > diagramValue) {
      _minData = diagramValue;
    }

    BarChartGroupData data = BarChartGroupData(x: exercise.dateAdd!.millisecondsSinceEpoch, barRods: [
      BarChartRodData(y: diagramValue, width: 12, colors: [Colors.lightBlue, Colors.lightBlueAccent])
    ]);
    _chartData.add(data);
    _origExerciseTypeId = exercise.exerciseTypeId!;
  }

  @override
  bool checkNewType(Exercise exercise) {
    return _origExerciseTypeId == null || _origExerciseTypeId != exercise.exerciseTypeId;
  }

  @override
  void iteration() {
    this.resetTemp();
    Exercise? tempExercise;
    inputList.forEach((element) {
      tempExercise = element;

      if (this.checkNewType(element)) {
        if (_origExerciseTypeId == null) {
          _basePercent = getBasePercent(element);
          this.addTempData(element);
        } else {
          this.temp2Output(element);
          this.resetTemp();
          _basePercent = getBasePercent(element);
          this.addTempData(element);
        }
      } else {
        this.addTempData(element);
      }
    });
    if (tempExercise != null) {
      this.temp2Output(tempExercise!);
    }
  }

  @override
  void temp2Output(Exercise exercise) {
    double dInterval = ((_maxData + _minData) / 8).roundToDouble();
    int gridInterval = (dInterval / 3).floor();

    ChartDataExtended extended;
    if (outputList[_origExerciseTypeId] == null) {
      extended = ChartDataExtended(data: _chartData, interval: dInterval, gridInterval: gridInterval);
      outputList[_origExerciseTypeId!] = extended;
    } else {
      extended = outputList[_origExerciseTypeId]!;
      _chartData.forEach((element) {
        extended.data.add(element);
      });
    }
  }

  @override
  void resetTemp() {
    _minData = 999999999999;
    _maxData = 0;
    _chartData = [];
  }
}

class ChartDataExtended {
  final List<BarChartGroupData> data;
  final double interval;
  final int gridInterval;

  const ChartDataExtended({required this.data, required this.interval, required this.gridInterval});

  Map<String, dynamic> toJson() {
    List listBarChartData = [];
    data.forEach((element) {
      element.barRods.forEach((rods) {
        var barChartData = {
          'x': element.x,
          'y': rods.y,
        };
        listBarChartData.add(barChartData);
      });
    });
    var chartData = {
      "data": listBarChartData.toString(),
      "interval": interval.toString(),
      "gridInterval": gridInterval,
    };
    return chartData;
  }
}

class DevelopmentByMuscleBloc extends Bloc<DevelopmentByMuscleEvent, DevelopmentByMuscleState> with Calculate, Logging {
  final WorkoutTreeRepository workoutTreeRepository;

  final ExerciseRepository exerciseRepository = ExerciseRepository();
  LinkedHashMap<int, ChartDataExtended> listChartData = LinkedHashMap();
  late List<BarChartGroupData> chartData;
  String diagramType = DiagramType.sumMass;
  String dateRate = DateRate.daily;
  double basePercent = 0;

  @override
  DevelopmentByMuscleBloc({required this.workoutTreeRepository}) : super(DevelopmentByMuscleStateInitial());

  Future<void> getData() async {
    workoutTreeRepository.sortedTree.clear();
    workoutTreeRepository.sortByMuscleType();

    workoutTreeRepository.sortedTree.forEach((key, value) {
      List<WorkoutMenuTree> listWorkoutTree = value;
      listWorkoutTree.forEach((workoutTree) {
        workoutTree.selected = false;
      });
    });

    this.getChartData();
  }

  void getChartData() {
    List<Exercise>? exercises = exerciseRepository.getExerciseList();

    //print("-- Start calculate --- ");
    exercises = this.groupByDate(exercises);

    exercises = sort(exercises, true);
/*    exercises.forEach((exercise) {
      print ("Chart exercise " + exercise.toJsonDatePart().toString());
    });*/

    listChartData = LinkedHashMap();
    GroupChart groupChart = GroupChart(inputList: exercises, outputList: listChartData);
    groupChart.diagramType = this.diagramType;
    groupChart.iteration();
    listChartData = groupChart.outputList;

    listChartData.forEach((key, value) {
      //trace("typeid " + key.toString() + " chardata " + value.toJson().toString());
    });
    return;
  }

  List<Exercise> groupByDate(List<Exercise>? exercises) {
    List<Exercise> groupedExercises = [];
    if (exercises != null) {
      exercises = sort(exercises, false);
      exercises.forEach((exercise) {
        //trace("Date exercise  " + exercise.toJsonDatePart().toString());
      });

      GroupDate groupDate = GroupDate(inputList: exercises, outputList: groupedExercises);
      groupDate.dateRate = this.dateRate;
      groupDate.diagramType = this.diagramType;
      groupDate.iteration();
      groupedExercises = groupDate.outputList;
    }

/*    groupedExercises.forEach((element) {
      print("Grouped " + element.toJsonDatePart().toString());
    });*/

    return groupedExercises;
  }

  List<Exercise> sort(List<Exercise> exercises, bool asc) {
    exercises.sort((a, b) {
      var aDateId = a.exerciseTypeId.toString() + "_" + a.datePart.toString();
      var bDateId = b.exerciseTypeId.toString() + "_" + b.datePart.toString();

      return asc ? aDateId.compareTo(bDateId) : bDateId.compareTo(aDateId);
    });
    return exercises;
  }

  String getDateFormat(DateTime datetime) {
    return DateFormat('yMd', AppLanguage().appLocal.toString()).format(datetime);
  }

  @override
  Stream<DevelopmentByMuscleState> mapEventToState(DevelopmentByMuscleEvent event) async* {
    try {
      if (event is DevelopmentByMuscleLoad) {
        yield DevelopmentByMuscleLoadingState();
        Track().track(TrackingEvent.my_muscle_development);
        Cache().setActivityDonePrefs(ActivityDone.isMuscleDevelopmentSeen);
        await getData();
        yield DevelopmentByMuscleReadyState();
      } else if (event is DevelopmentByMuscleDiagramTypeChange) {
        yield DevelopmentByMuscleLoadingState();
        String type = event.diagramType;
        this.diagramType = type;
        getChartData();
        yield DevelopmentByMuscleReadyState();
      } else if (event is DevelopmentByMuscleDateRateChange) {
        yield DevelopmentByMuscleLoadingState();
        String dateRate = event.dateRate;
        this.dateRate = dateRate;
        getChartData();
        yield DevelopmentByMuscleReadyState();
      }
    } on Exception catch (e) {
      yield DevelopmentByMuscleErrorState(message: e.toString());
    }
  }
}
