import 'package:charts_flutter/flutter.dart' as charts;
import 'package:diameter/models/basal.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';

charts.TimeSeriesChart generateChart(
    DateTime date,
    List<LogEntry> logEntries,
    List<LogEvent> logEvents,
    List<GlucoseTarget> targets,
    bool showMeals,
    bool showBasal,
    bool showBolus) {
  final targetRange = targets.singleWhere((target) =>
      Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
          ? Settings.targetMgPerDl >= target.fromMgPerDL &&
              Settings.targetMgPerDl <= target.toMgPerDl
          : Settings.targetMmolPerL >= target.fromMmolPerL &&
              Settings.targetMmolPerL <= target.toMmolPerL);

  final List<Basal> basalRate = [];

  if (showBasal) {
    Basal? lastBasal;

    for (var time = date;
        time.isBefore(date.add(const Duration(days: 1)));
        time = time.add(const Duration(minutes: 1))) {
      final basal = Basal.getRateForTime(time);
      if (basal != null && basal.units != lastBasal?.units) {
        if (basalRate.isNotEmpty) {
          basalRate.last.endTime = time;
        }
        basalRate.add(
            Basal(startTime: time, endTime: basal.endTime, units: basal.units));
        lastBasal = basal;
      }
    }
  }

  return charts.TimeSeriesChart(
    [
      charts.Series<LogEntry, DateTime>(
        id: 'Glucose',
        colorFn: (LogEntry entry, _) => charts.Color.black,
        domainFn: (LogEntry entry, _) => entry.time,
        measureFn: (LogEntry entry, _) =>
            Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
                ? entry.mgPerDl
                : entry.mmolPerL,
        data: logEntries,
      )..setAttribute(charts.rendererIdKey, 'glucoseRenderer'),
      charts.Series<LogEntry, DateTime>(
        id: 'Carbohydrates',
        colorFn: (LogEntry entry, _) =>
            charts.MaterialPalette.yellow.shadeDefault,
        domainFn: (LogEntry entry, _) => entry.time,
        measureFn: (LogEntry entry, _) =>
            LogMeal.getTotalCarbsForEntry(entry.id),
        data: showMeals ? logEntries : [],
      )..setAttribute(charts.rendererIdKey, 'carbsRenderer'),
      charts.Series<Basal, DateTime>(
        id: 'Basal',
        colorFn: (Basal basal, _) => charts.Color.black.lighter,
        domainFn: (Basal basal, _) => basal.startTime,
        measureFn: (Basal basal, _) => basal.units,
        data: showBasal ? basalRate : [],
      )
        ..setAttribute(
            charts.measureAxisIdKey, charts.Axis.secondaryMeasureAxisId)
        ..setAttribute(charts.rendererIdKey, 'basalRenderer'),
      charts.Series<LogEntry, DateTime>(
        id: 'Bolus',
        colorFn: (LogEntry entry, _) =>
            charts.MaterialPalette.blue.shadeDefault,
        domainFn: (LogEntry entry, _) => entry.time,
        measureFn: (LogEntry entry, _) =>
            LogBolus.getTotalBolusForEntry(entry.id),
        data: showBolus ? logEntries : [],
      )
        ..setAttribute(
            charts.measureAxisIdKey, charts.Axis.secondaryMeasureAxisId)
        ..setAttribute(charts.rendererIdKey, 'bolusRenderer'),
    ],
    domainAxis: const charts.DateTimeAxisSpec(
      tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
        hour: charts.TimeFormatterSpec(
          format: 'HH',
          transitionFormat: 'HH',
        ),
      ),
    ),
    primaryMeasureAxis: const charts.NumericAxisSpec(
      tickProviderSpec: charts.BasicNumericTickProviderSpec(
        desiredTickCount: 5,
      ),
    ),
    secondaryMeasureAxis: const charts.NumericAxisSpec(
      tickProviderSpec: charts.BasicNumericTickProviderSpec(
        desiredTickCount: 5,
      ),
    ),
    animate: false,
    customSeriesRenderers: [
      charts.LineRendererConfig(
        includeLine: true,
        includePoints: false,
        customRendererId: 'glucoseRenderer',
      ),
      charts.LineRendererConfig(
        includeLine: true,
        includePoints: false,
        includeArea: true,
        roundEndCaps: true,
        strokeWidthPx: 1,
        customRendererId: 'basalRenderer',
      ),
      charts.LineRendererConfig(
        includeLine: false,
        includePoints: true,
        customRendererId: 'bolusRenderer',
      ),
      charts.LineRendererConfig(
        includeLine: false,
        includePoints: true,
        customRendererId: 'carbsRenderer',
      ),
    ],
    behaviors: [
      charts.RangeAnnotation([
        charts.RangeAnnotationSegment<num>(
          Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
              ? targetRange.fromMgPerDL
              : targetRange.fromMmolPerL,
          Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
              ? targetRange.toMgPerDl
              : targetRange.toMmolPerL,
          charts.RangeAnnotationAxisType.measure,
          color: charts.MaterialPalette.green.shadeDefault.lighter,
        ),
        ...logEvents
            .where((element) => element.hasEndTime)
            .map((LogEvent event) => charts.RangeAnnotationSegment<DateTime>(
                  event.time,
                  event.endTime ?? DateTime.now(),
                  charts.RangeAnnotationAxisType.domain,
                  startLabel: event.eventType.target?.value,
                  color: charts.MaterialPalette.gray.shade300,
                ))
            .toList(),
        ...logEvents
            .where((element) => !element.hasEndTime)
            .map((LogEvent event) => charts.LineAnnotationSegment<DateTime>(
                  event.time,
                  charts.RangeAnnotationAxisType.domain,
                  startLabel: '${event.eventType.target?.value}',
                ))
            .toList(),
      ]),
    ],
  );
}

class DailyChart extends StatefulWidget {
  static const String routeName = '/reports/base';
  const DailyChart({Key? key}) : super(key: key);

  @override
  _DailyChartState createState() => _DailyChartState();
}

class _DailyChartState extends State<DailyChart> {
  late List<LogEntry> logEntries;
  late List<LogEvent> logEvents;
  late List<GlucoseTarget> targets;

  final ScrollController _scrollController = ScrollController();
  final TextEditingController _dateController = TextEditingController(text: '');

  late DateTime date;

  String? swipeDirection;

  bool showChart = true;
  bool showBolus = true;
  bool showBasal = true;
  bool showMeals = true;

  @override
  void initState() {
    super.initState();

    date = DateTimeUtils.today();
    logEntries = LogEntry.getAllForDate(date);
    logEvents = LogEvent.getAllForDate(date);
    targets = GlucoseTarget.getAll();
    _dateController.text = DateTimeUtils.displayDate(date);
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _dateController.dispose();
    super.dispose();
  }

  void reload({String? message}) {
    setState(() {
      logEntries = LogEntry.getAllForDate(date);
      logEvents = LogEvent.getAllForDate(date);
    });
    setState(() {
      if (message != null) {
        var snackBar = SnackBar(
          content: Text(message),
          duration: const Duration(seconds: 2),
        );
        ScaffoldMessenger.of(context)
          ..removeCurrentSnackBar()
          ..showSnackBar(snackBar);
      }
    });
  }

  void onChangeDate(DateTime? newDate) {
    if (newDate != null) {
      setState(() {
        date = DateTime(newDate.year, newDate.month, newDate.day);
        _dateController.text = DateTimeUtils.displayDate(newDate);
      });
      reload();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Daily Chart'),
        actions: <Widget>[
          IconButton(
              onPressed: () => onChangeDate(DateTime.now()),
              icon: const Icon(Icons.today)),
          IconButton(
              onPressed: () => showDialog(
                    context: context,
                    builder: (context) => AlertDialog(
                        content: Column(
                          mainAxisSize: MainAxisSize.min,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: [
                            CheckboxListTile(
                              value: showChart,
                              onChanged: (_) =>
                                  setState(() => showChart = !showChart),
                              title: const Text('show Chart'),
                              controlAffinity: ListTileControlAffinity.leading,
                            ),
                            Padding(
                              padding: const EdgeInsets.only(left: 20.0),
                              child: CheckboxListTile(
                                value: showBolus,
                                onChanged: showChart
                                    ? (_) =>
                                        setState(() => showBolus = !showBolus)
                                    : null,
                                title: const Text('show Bolus'),
                                controlAffinity:
                                    ListTileControlAffinity.leading,
                              ),
                            ),
                            Padding(
                              padding: const EdgeInsets.only(left: 20.0),
                              child: CheckboxListTile(
                                value: showBasal,
                                onChanged: showChart
                                    ? (_) =>
                                        setState(() => showBasal = !showBasal)
                                    : null,
                                title: const Text('show Basal'),
                                controlAffinity:
                                    ListTileControlAffinity.leading,
                              ),
                            ),
                            Padding(
                              padding: const EdgeInsets.only(left: 20.0),
                              child: CheckboxListTile(
                                value: showMeals,
                                onChanged: showChart
                                    ? (_) =>
                                        setState(() => showMeals = !showMeals)
                                    : null,
                                title: const Text('show Meals'),
                                controlAffinity:
                                    ListTileControlAffinity.leading,
                              ),
                            ),
                          ],
                        ),
                        actions: <Widget>[
                          ElevatedButton(
                            onPressed: () => Navigator.pop(context),
                            child: const Text('CLOSE'),
                          ),
                        ]),
                  ),
              icon: const Icon(Icons.settings)),
          IconButton(
            onPressed: reload,
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      drawer: const Navigation(currentLocation: DailyChart.routeName),
      body: GestureDetector(
        onPanUpdate: (details) {
          swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
        },
        onPanEnd: (details) {
          if (swipeDirection == null) {
            return;
          }
          if (swipeDirection == 'right' &&
              !date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
            onChangeDate(date.subtract(const Duration(days: 1)));
          }
          if (swipeDirection == 'left' &&
              date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
            onChangeDate(date.add(const Duration(days: 1)));
          }
        },
        child: Column(
          children: [
            Row(
              children: [
                IconButton(
                  onPressed: date.isAtSameMomentAs(DateTime(2000, 1, 1))
                      ? null
                      : () =>
                          onChangeDate(date.subtract(const Duration(days: 1))),
                  icon: const Icon(Icons.arrow_back),
                ),
                Expanded(
                  child: GestureDetector(
                    onTap: () async {
                      final newTime = await showDatePicker(
                        context: context,
                        initialDate: date,
                        firstDate: DateTime(2000, 1, 1),
                        lastDate: DateTime.now().add(const Duration(days: 365)),
                      );
                      onChangeDate(newTime);
                    },
                    child: Text(
                      DateTimeUtils.displayDate(date).toUpperCase(),
                      style: Theme.of(context).textTheme.subtitle2,
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
                IconButton(
                  onPressed:
                      date.add(const Duration(days: 1)).isBefore(DateTime.now())
                          ? () => onChangeDate(date.add(const Duration(days: 1)))
                          : null,
                  icon: const Icon(Icons.arrow_forward),
                ),
              ],
            ),
            Expanded(
              child: logEntries.isNotEmpty
                  ? generateChart(date, logEntries, logEvents, targets, showMeals,
                      showBasal, showBolus)
                  : const Center(
                      child: Text(
                          'You have not created any Log Entries for this date yet!'),
                    ),
            ),
          ],
        ),
      ),
    );
  }
}
