/*
Copyright 2023-2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of FitnessTrax.

FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::{
    components::{distance_field, duration_field, time_field},
    types::{DistanceFormatter, DurationFormatter, FormatOption, TimeFormatter},
};
use dimensioned::si;
use ft_core::{TimeDistance, TimeDistanceActivity, TIME_DISTANCE_ACTIVITIES};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc};

pub fn time_distance_summary(
    activity: TimeDistanceActivity,
    distance: DistanceFormatter,
    duration: DurationFormatter,
) -> Option<gtk::Label> {
    let text = match (*distance > si::M, *duration > si::S) {
        (true, true) => Some(format!(
            "{} of {:?} in {}",
            distance.format(FormatOption::Full),
            activity,
            duration.format(FormatOption::Full)
        )),
        (true, false) => Some(format!(
            "{} of {:?}",
            distance.format(FormatOption::Full),
            activity
        )),
        (false, true) => Some(format!(
            "{} of {:?}",
            duration.format(FormatOption::Full),
            activity
        )),
        (false, false) => None,
    };

    text.map(|text| gtk::Label::builder().halign(gtk::Align::Start).label(&text).build())
}

pub fn time_distance_detail(record: ft_core::TimeDistance) -> gtk::Box {
    let layout = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .hexpand(true)
        .build();
    let first_row = gtk::Box::builder().homogeneous(true).build();

    first_row.append(
        &gtk::Label::builder()
            .halign(gtk::Align::Start)
            .label(record.datetime.format("%H:%M").to_string())
            .build(),
    );

    first_row.append(
        &gtk::Label::builder()
            .halign(gtk::Align::Start)
            .label(format!("{:?}", record.activity))
            .build(),
    );

    first_row.append(
        &gtk::Label::builder()
            .halign(gtk::Align::Start)
            .label(
                record
                    .distance
                    .map(|dist| DistanceFormatter::from(dist).format(FormatOption::Abbreviated))
                    .unwrap_or("".to_owned()),
            )
            .build(),
    );

    first_row.append(
        &gtk::Label::builder()
            .halign(gtk::Align::Start)
            .label(
                record
                    .duration
                    .map(|duration| {
                        DurationFormatter::from(duration).format(FormatOption::Abbreviated)
                    })
                    .unwrap_or("".to_owned()),
            )
            .build(),
    );

    layout.append(&first_row);

    layout.append(
        &gtk::Label::builder()
            .halign(gtk::Align::Start)
            .label(
                record
                    .comments
                    .map(|comments| comments.to_string())
                    .unwrap_or("".to_owned()),
            )
            .build(),
    );

    layout
}

type OnUpdate = Rc<RefCell<Box<dyn Fn(TimeDistance)>>>;

pub struct TimeDistanceEditPrivate {
    #[allow(unused)]
    workout: RefCell<ft_core::TimeDistance>,
    on_update: OnUpdate,
}

impl Default for TimeDistanceEditPrivate {
    fn default() -> Self {
        Self {
            workout: RefCell::new(TimeDistance {
                datetime: chrono::Utc::now().into(),
                activity: TimeDistanceActivity::Biking,
                duration: None,
                distance: None,
                comments: None,
            }),
            on_update: Rc::new(RefCell::new(Box::new(|_| {}))),
        }
    }
}

#[glib::object_subclass]
impl ObjectSubclass for TimeDistanceEditPrivate {
    const NAME: &'static str = "TimeDistanceEdit";
    type Type = TimeDistanceEdit;
    type ParentType = gtk::Box;
}

impl ObjectImpl for TimeDistanceEditPrivate {}
impl WidgetImpl for TimeDistanceEditPrivate {}
impl BoxImpl for TimeDistanceEditPrivate {}

glib::wrapper! {
    pub struct TimeDistanceEdit(ObjectSubclass<TimeDistanceEditPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
}

impl Default for TimeDistanceEdit {
    fn default() -> Self {
        let s: Self = Object::builder().build();
        s.set_orientation(gtk::Orientation::Vertical);
        s.set_hexpand(true);
        s.set_css_classes(&["time-distance-edit"]);

        s
    }
}

impl TimeDistanceEdit {
    pub fn new<OnUpdate>(workout: TimeDistance, on_update: OnUpdate) -> Self
    where
        OnUpdate: Fn(TimeDistance) + 'static,
    {
        let s = Self::default();

        *s.imp().workout.borrow_mut() = workout.clone();
        *s.imp().on_update.borrow_mut() = Box::new(on_update);

        let details_row = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .build();

        details_row.append(
            &time_field(
                Some(TimeFormatter::from(workout.datetime.naive_local().time())),
                {
                    let s = s.clone();
                    move |t| s.update_time(t)
                },
            )
            .widget(),
        );
        details_row.append(&s.activity_menu(workout.activity));
        details_row.append(
            &distance_field(workout.distance.map(DistanceFormatter::from), {
                let s = s.clone();
                move |d| s.update_distance(d)
            })
            .widget(),
        );
        details_row.append(
            &duration_field(workout.duration.map(DurationFormatter::from), {
                let s = s.clone();
                move |d| s.update_duration(d)
            })
            .widget(),
        );
        s.append(&details_row);
        s.append(&gtk::Entry::new());

        s
    }

    fn update_time(&self, time: Option<TimeFormatter>) {
        if let Some(time_formatter) = time {
            let mut workout = self.imp().workout.borrow_mut();
            let tz = workout.datetime.timezone();
            let new_time = workout
                .datetime
                .date_naive()
                .and_time(*time_formatter)
                .and_local_timezone(tz)
                .unwrap()
                .fixed_offset();
            workout.datetime = new_time;
            (self.imp().on_update.borrow())(workout.clone());
        }
    }

    fn update_workout_type(&self, type_: TimeDistanceActivity) {
        let mut workout = self.imp().workout.borrow_mut();
        workout.activity = type_;
        (self.imp().on_update.borrow())(workout.clone())
    }

    fn update_distance(&self, distance: Option<DistanceFormatter>) {
        let mut workout = self.imp().workout.borrow_mut();
        workout.distance = distance.map(|d| *d);
        (self.imp().on_update.borrow())(workout.clone());
    }

    fn update_duration(&self, duration: Option<DurationFormatter>) {
        let mut workout = self.imp().workout.borrow_mut();
        workout.duration = duration.map(|d| *d);
        (self.imp().on_update.borrow())(workout.clone());
    }

    fn activity_menu(&self, selected: TimeDistanceActivity) -> gtk::DropDown {
        let options = TIME_DISTANCE_ACTIVITIES
            .iter()
            .map(|item| format!("{:?}", item))
            .collect::<Vec<String>>();

        let options = options.iter().map(|o| o.as_ref()).collect::<Vec<&str>>();

        let selected_idx = TIME_DISTANCE_ACTIVITIES
            .iter()
            .position(|&v| v == selected)
            .unwrap_or(0);

        let menu = gtk::DropDown::from_strings(&options);
        menu.set_selected(selected_idx as u32);
        menu.connect_selected_item_notify({
            let s = self.clone();
            move |menu| {
                let new_item = TIME_DISTANCE_ACTIVITIES[menu.selected() as usize];
                s.update_workout_type(new_item);
            }
        });
        menu
    }
}
