use crate::data::Promise;
use druid::{
    lens::{Field, Map},
    widget::ListIter,
    Data, Lens, LensExt,
};

#[derive(Clone, Data)]
pub struct Ctx<C: Data, T: Data> {
    pub ctx: C,
    pub data: T,
}

impl<C: Data, T: Data> Ctx<C, T> {
    pub fn new(c: C, t: T) -> Self {
        Self { ctx: c, data: t }
    }

    pub fn make<S: Data>(cl: impl Lens<S, C>, tl: impl Lens<S, T>) -> impl Lens<S, Self> {
        CtxMake { cl, tl }
    }

    pub fn ctx() -> impl Lens<Self, C> {
        Field::new(|c: &Self| &c.ctx, |c: &mut Self| &mut c.ctx)
    }

    pub fn data() -> impl Lens<Self, T> {
        Field::new(|c: &Self| &c.data, |c: &mut Self| &mut c.data)
    }

    pub fn map<U: Data>(map: impl Lens<T, U>) -> impl Lens<Self, Ctx<C, U>> {
        CtxMap { map }
    }
}

struct CtxMake<CL, TL> {
    cl: CL,
    tl: TL,
}

impl<C: Data, T: Data, S: Data, CL, TL> Lens<S, Ctx<C, T>> for CtxMake<CL, TL>
where
    CL: Lens<S, C>,
    TL: Lens<S, T>,
{
    fn with<V, F: FnOnce(&Ctx<C, T>) -> V>(&self, data: &S, f: F) -> V {
        let c = self.cl.get(data);
        let t = self.tl.get(data);
        let ct = Ctx::new(c, t);
        f(&ct)
    }

    fn with_mut<V, F: FnOnce(&mut Ctx<C, T>) -> V>(&self, data: &mut S, f: F) -> V {
        let c = self.cl.get(data);
        let t = self.tl.get(data);
        let mut ct = Ctx::new(c, t);
        let v = f(&mut ct);
        self.cl.put(data, ct.ctx);
        self.tl.put(data, ct.data);
        v
    }
}

struct CtxMap<Map> {
    map: Map,
}

impl<C: Data, T: Data, U: Data, Map> Lens<Ctx<C, T>, Ctx<C, U>> for CtxMap<Map>
where
    Map: Lens<T, U>,
{
    fn with<V, F: FnOnce(&Ctx<C, U>) -> V>(&self, c: &Ctx<C, T>, f: F) -> V {
        self.map.with(&c.data, |u| {
            let cu = Ctx::new(c.ctx.to_owned(), u.to_owned());
            f(&cu)
        })
    }

    fn with_mut<V, F: FnOnce(&mut Ctx<C, U>) -> V>(&self, c: &mut Ctx<C, T>, f: F) -> V {
        let t = &mut c.data;
        let c = &mut c.ctx;
        self.map.with_mut(t, |u| {
            let mut cu = Ctx::new(c.to_owned(), u.to_owned());
            let v = f(&mut cu);
            *c = cu.ctx;
            *u = cu.data;
            v
        })
    }
}

impl<C: Data, PT: Data, PD: Data, PE: Data> Ctx<C, Promise<PT, PD, PE>> {
    pub fn in_promise() -> impl Lens<Self, Promise<Ctx<C, PT>, Ctx<C, PD>, Ctx<C, PE>>> {
        Map::new(
            |c: &Self| match &c.data {
                Promise::Empty => Promise::Empty,
                Promise::Resolved(res) => {
                    Promise::Resolved(Ctx::new(c.ctx.to_owned(), res.to_owned()))
                }
                Promise::Deferred(def) => {
                    Promise::Deferred(Ctx::new(c.ctx.to_owned(), def.to_owned()))
                }
                Promise::Rejected(err) => {
                    Promise::Rejected(Ctx::new(c.ctx.to_owned(), err.to_owned()))
                }
            },
            |c: &mut Self, p: Promise<Ctx<C, PT>, Ctx<C, PD>, Ctx<C, PE>>| match p {
                Promise::Empty => {
                    c.data = Promise::Empty;
                }
                Promise::Resolved(pc) => {
                    c.data = Promise::Resolved(pc.data);
                    c.ctx = pc.ctx;
                }
                Promise::Deferred(pc) => {
                    c.data = Promise::Deferred(pc.data);
                    c.ctx = pc.ctx;
                }
                Promise::Rejected(pc) => {
                    c.data = Promise::Rejected(pc.data);
                    c.ctx = pc.ctx;
                }
            },
        )
    }
}

impl<C: Data, T: Data, L: ListIter<T>> ListIter<Ctx<C, T>> for Ctx<C, L> {
    fn for_each(&self, mut cb: impl FnMut(&Ctx<C, T>, usize)) {
        self.data.for_each(|item, index| {
            let d = Ctx::new(self.ctx.to_owned(), item.to_owned());
            cb(&d, index);
        });
    }

    fn for_each_mut(&mut self, mut cb: impl FnMut(&mut Ctx<C, T>, usize)) {
        let ctx = &mut self.ctx;
        let data = &mut self.data;
        data.for_each_mut(|item, index| {
            let mut d = Ctx::new(ctx.to_owned(), item.to_owned());
            cb(&mut d, index);
            if !ctx.same(&d.ctx) {
                *ctx = d.ctx;
            }
            if !item.same(&d.data) {
                *item = d.data;
            }
        });
    }

    fn data_len(&self) -> usize {
        self.data.data_len()
    }
}
