
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';

import '../../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart';
import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart';
import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
import '../../../model/timetable/timetableProps.dart';
import '../../../storage/base/settingsProvider.dart';
import '../../../widget/loadingSpinner.dart';
import '../../../widget/placeholderView.dart';
import 'appointmenetComponent.dart';
import 'appointmentDetails.dart';
import 'timeRegionComponent.dart';
import 'timetableEvents.dart';

class Timetable extends StatefulWidget {
  const Timetable({Key? key}) : super(key: key);

  @override
  State<Timetable> createState() => _TimetableState();
}

class _TimetableState extends State<Timetable> {
  CalendarController controller = CalendarController();

  double elementScale = 40;
  double baseElementScale = 40;

  late final SettingsProvider settings;

  @override
  void initState() {
    settings = Provider.of<SettingsProvider>(context, listen: false);
    elementScale = baseElementScale = settings.val().timetableSettings.zoom;

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      Provider.of<TimetableProps>(context, listen: false).run();
    });

    controller.displayDate = DateTime.now().add(const Duration(days: 2));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: const Text("Stunden & Vertretungsplan"),
        actions: [
          IconButton(
              icon: const Icon(Icons.home_outlined),
              onPressed: () {
                controller.displayDate = DateTime.now().add(const Duration(days: 2));
              }
          ),
        ],
      ),
      body: Consumer<TimetableProps>(
        builder: (context, value, child) {

          if(value.hasError) {
            return PlaceholderView(
              icon: Icons.calendar_month,
              text: "Webuntis error: ${value.error.toString()}",
              button: TextButton(
                child: const Text("Neu laden"),
                onPressed: () {
                  Provider.of<TimetableProps>(context, listen: false).resetWeek();
                },
              ),
            );
          }

          if(value.primaryLoading()) return const LoadingSpinner();

          GetHolidaysResponse holidays = value.getHolidaysResponse;

          return GestureDetector(
            onScaleStart: (details) => baseElementScale = elementScale,
            onScaleUpdate: (details) {
              setState(() {
                elementScale = (baseElementScale * details.scale).clamp(40, 80);
              });
            },
            onScaleEnd: (details) {
              settings.val(write: true).timetableSettings.zoom = elementScale;
            },

            child: SfCalendar(
              view: CalendarView.workWeek,
              dataSource: _buildTableEvents(value),

              maxDate: DateTime.now().add(const Duration(days: 7)),
              minDate: DateTime.now().subtract(const Duration (days: 14)),

              controller: controller,

              onViewChanged: (ViewChangedDetails details) {
                WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
                  Provider.of<TimetableProps>(context, listen: false).updateWeek(details.visibleDates.first, details.visibleDates.last);
                });
              },

              onTap: (calendarTapDetails) {
                if(calendarTapDetails.appointments == null) return;
                Appointment tapped = calendarTapDetails.appointments!.first;
                AppointmentDetails.show(context, value, tapped);
              },

              firstDayOfWeek: DateTime.monday,
              specialRegions: _buildSpecialTimeRegions(holidays),
              timeSlotViewSettings: TimeSlotViewSettings(
                startHour: 07.5,
                endHour: 16.5,
                timeInterval: const Duration(minutes: 30),
                timeFormat: "HH:mm",
                dayFormat: "EE",
                timeIntervalHeight: elementScale,
              ),

              timeRegionBuilder: (BuildContext context, TimeRegionDetails timeRegionDetails) => TimeRegionComponent(details: timeRegionDetails),
              appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) => AppointmentComponent(
                  details: details,
                  crossedOut: _isCrossedOut(details)
              ),

              headerHeight: 0,
              selectionDecoration: const BoxDecoration(),

              allowAppointmentResize: false,
              allowDragAndDrop: false,
              allowViewNavigation: false,
            ),
          );
        },
      ),
    );
  }

  List<TimeRegion> _buildSpecialTimeRegions(GetHolidaysResponse holidays, ) {
    DateTime lastMonday = DateTime.now().subtract(Duration(days: DateTime.now().weekday - 1));
    DateTime firstBreak = lastMonday.copyWith(hour: 10, minute: 15);
    DateTime secondBreak = lastMonday.copyWith(hour: 13, minute: 50);
    DateTime beforeSchool = lastMonday.copyWith(hour: 7, minute: 30);

    return [
      ...holidays.result.map((e) {
        return TimeRegion(
            startTime: _parseWebuntisTimestamp(e.startDate, 755),
            endTime: _parseWebuntisTimestamp(e.startDate, 1630),
            text: 'holiday:${e.name}',
            color: Theme.of(context).disabledColor.withAlpha(50),
            iconData: Icons.holiday_village_outlined
        );
      }),

      TimeRegion(
          startTime: firstBreak,
          endTime: firstBreak.add(const Duration(minutes: 20)),
          recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5',
          text: 'centerIcon',
          color: Theme.of(context).primaryColor.withAlpha(50),
          iconData: Icons.restaurant
      ),
      TimeRegion(
          startTime: secondBreak,
          endTime: secondBreak.add(const Duration(minutes: 15)),
          recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5',
          text: 'centerIcon',
          color: Theme.of(context).primaryColor.withAlpha(50),
          iconData: Icons.restaurant
      ),
      TimeRegion(
        startTime: beforeSchool,
        endTime: beforeSchool.add(const Duration(minutes: 25)),
        recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5',
        color: Theme.of(context).disabledColor.withAlpha(50),
        text: "centerIcon",
      ),
    ];
  }

  TimetableEvents _buildTableEvents(TimetableProps data) {

    List<Appointment> appointments = data.getTimetableResponse.result.map((element) {

      GetRoomsResponse rooms = data.getRoomsResponse;
      GetSubjectsResponse subjects = data.getSubjectsResponse;

      try {
        DateTime startTime = _parseWebuntisTimestamp(element.date, element.startTime);
        DateTime endTime = _parseWebuntisTimestamp(element.date, element.endTime);
        return Appointment(
          id: element,
          startTime: startTime,
          endTime: endTime,
          subject: subjects.result.firstWhere((subject) => subject.id == element.su[0].id).name,
          location: ""
              "${rooms.result.firstWhere((room) => room.id == element.ro[0].id).name}"
              "\n"
              "${element.te.first.longname}",
          notes: element.activityType,
          color: _getEventColor(element, startTime, endTime),
        );
      } catch(e) {
        return Appointment(
          id: element,
          startTime: _parseWebuntisTimestamp(element.date, element.startTime),
          endTime: _parseWebuntisTimestamp(element.date, element.endTime),
          subject: "Änderung",
          notes: element.info,
          location: 'Unbekannt',
          color: Theme.of(context).primaryColor,
          startTimeZone: '',
          endTimeZone: '',
        );
      }
    }).toList();

    return TimetableEvents(appointments);
  }

  DateTime _parseWebuntisTimestamp(int date, int time) {
    String timeString = time.toString().padLeft(4, '0');
    return DateTime.parse('$date ${timeString.substring(0, 2)}:${timeString.substring(2, 4)}');
  }

  Color _getEventColor(GetTimetableResponseObject webuntisElement, DateTime startTime, DateTime endTime) {
    // Make element darker, when it already took place
    int opacity = endTime.isBefore(DateTime.now()) ? 100 : 255;

    // Cancelled
    if(webuntisElement.code == "cancelled") return const Color(0xff000000).withAlpha(opacity);

    // Any changes or no teacher at this element
    if(webuntisElement.code == "irregular" || webuntisElement.te.first.id == 0) return const Color(0xff8F19B3).withAlpha(opacity);

    // Event was in the past
    if(endTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withAlpha(opacity);

    // Event takes currently place
    if(endTime.isAfter(DateTime.now()) && startTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withRed(100);

    // Fallback
    return Theme.of(context).primaryColor;
  }

  bool _isCrossedOut(CalendarAppointmentDetails calendarEntry) {
    GetTimetableResponseObject webuntisElement = (calendarEntry.appointments.first.id as GetTimetableResponseObject);

    return webuntisElement.code == "cancelled";
  }
}
