use std::cell::RefCell;

use gdk::glib::Object;
use gdk::subclass::prelude::ObjectSubclassIsExt;

gtk::glib::wrapper! {
    pub struct Leg(ObjectSubclass<imp::Leg>);
}

impl Leg {
    pub fn new(leg: rcore::Leg) -> Self {
        let s: Self = Object::builder::<Self>().build();
        s.imp().leg.swap(&RefCell::new(Some(leg)));
        s
    }

    pub fn leg(&self) -> rcore::Leg {
        self.imp()
            .leg
            .borrow()
            .clone()
            .expect("Leg has not yet been set up")
    }
}

mod imp {
    use chrono::Local;
    use gtk::glib;
    use std::cell::RefCell;

    use gdk::{
        glib::{
            ParamSpec, ParamSpecBoolean, ParamSpecEnum, ParamSpecObject, ParamSpecString, Value,
        },
        prelude::{ObjectExt, ParamSpecBuilderExt, ToValue},
        subclass::prelude::{ObjectImpl, ObjectSubclass, ObjectSubclassExt},
    };
    use once_cell::sync::Lazy;

    use crate::backend::{Frequency, LateFactor, LoadFactor, Place};

    #[derive(Default)]
    pub struct Leg {
        pub(super) leg: RefCell<Option<rcore::Leg>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Leg {
        const NAME: &'static str = "DBLeg";
        type Type = super::Leg;
    }

    impl ObjectImpl for Leg {
        fn properties() -> &'static [ParamSpec] {
            static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
                vec![
                    ParamSpecString::builder("direction").read_only().build(),
                    ParamSpecString::builder("name").read_only().build(),
                    ParamSpecString::builder("departure").read_only().build(),
                    ParamSpecString::builder("arrival").read_only().build(),
                    ParamSpecString::builder("planned-departure")
                        .read_only()
                        .build(),
                    ParamSpecString::builder("planned-arrival")
                        .read_only()
                        .build(),
                    ParamSpecString::builder("departure-platform")
                        .read_only()
                        .build(),
                    ParamSpecString::builder("arrival-platform")
                        .read_only()
                        .build(),
                    ParamSpecString::builder("planned-departure-platform")
                        .read_only()
                        .build(),
                    ParamSpecString::builder("planned-arrival-platform")
                        .read_only()
                        .build(),
                    ParamSpecObject::builder::<Place>("origin")
                        .read_only()
                        .build(),
                    ParamSpecObject::builder::<Place>("destination")
                        .read_only()
                        .build(),
                    ParamSpecEnum::builder::<LoadFactor>("load-factor")
                        .read_only()
                        .build(),
                    ParamSpecEnum::builder::<LateFactor>("late-factor")
                        .read_only()
                        .build(),
                    ParamSpecObject::builder::<Frequency>("frequency")
                        .read_only()
                        .build(),
                    ParamSpecBoolean::builder("change-platform")
                        .read_only()
                        .build(),
                    ParamSpecBoolean::builder("is-unreachable")
                        .read_only()
                        .build(),
                    ParamSpecBoolean::builder("is-cancelled")
                        .read_only()
                        .build(),
                ]
            });
            PROPERTIES.as_ref()
        }

        fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {}

        fn property(&self, _id: usize, pspec: &ParamSpec) -> Value {
            let obj = self.obj();
            match pspec.name() {
                "direction" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.direction.as_ref())
                    .unwrap_or(
                        &obj.property::<Place>("destination")
                            .name()
                            .unwrap_or_default(),
                    )
                    .to_value(),
                "name" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.line.as_ref())
                    .and_then(|o| o.name.as_ref())
                    .unwrap_or(&gettextrs::gettext("Walk"))
                    .to_value(),
                "departure" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.departure)
                    .map(|d| d.with_timezone(&Local).format("%H:%M").to_string())
                    .to_value(),
                "arrival" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.arrival)
                    .map(|d| d.with_timezone(&Local).format("%H:%M").to_string())
                    .to_value(),
                "planned-departure" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.planned_departure)
                    .map(|d| d.with_timezone(&Local).format("%H:%M").to_string())
                    .to_value(),
                "planned-arrival" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.planned_arrival)
                    .map(|d| d.with_timezone(&Local).format("%H:%M").to_string())
                    .to_value(),
                "departure-platform" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.departure_platform.clone())
                    .to_value(),
                "arrival-platform" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.arrival_platform.clone())
                    .to_value(),
                "planned-departure-platform" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.planned_departure_platform.clone())
                    .to_value(),
                "planned-arrival-platform" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.planned_arrival_platform.clone())
                    .to_value(),
                "origin" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| Place::new(o.origin.clone()))
                    .to_value(),
                "destination" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| Place::new(o.destination.clone()))
                    .to_value(),
                "load-factor" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| LoadFactor::from(o.load_factor))
                    .unwrap_or_default()
                    .to_value(),
                "late-factor" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| {
                        std::cmp::max(
                            match (o.arrival, o.planned_arrival) {
                                (Some(real), Some(planned)) => LateFactor::from(real - planned),
                                _ => LateFactor::default(),
                            },
                            match (o.departure, o.planned_departure) {
                                (Some(real), Some(planned)) => LateFactor::from(real - planned),
                                _ => LateFactor::default(),
                            },
                        )
                    })
                    .unwrap_or_default()
                    .to_value(),
                "frequency" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .and_then(|o| o.frequency.clone())
                    .map(Frequency::new)
                    .to_value(),
                "change-platform" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| {
                        o.departure_platform != o.planned_departure_platform
                            || o.arrival_platform != o.planned_arrival_platform
                    })
                    .unwrap_or_default()
                    .to_value(),
                "is-unreachable" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| !o.reachable)
                    .unwrap_or_default()
                    .to_value(),
                "is-cancelled" => self
                    .leg
                    .borrow()
                    .as_ref()
                    .map(|o| o.cancelled)
                    .unwrap_or_default()
                    .to_value(),
                _ => unimplemented!(),
            }
        }
    }
}
