// timespan - A simple timespan for chrono times.
//
// Copyright (C) 2017
//     Fin Christensen <fin.christensen@posteo.de>
//
// This program 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.
//
// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

use crate::DelayedFormat;
use crate::Error;
use crate::Formatable;
use crate::Parsable;
use crate::Spanable;
use chrono::Duration;
use regex;
use regex::Regex;
use std;

/// This describes a span of something that is `Spanable` by providing a start and end point.
///
/// When the provided `Spanable` type `T` is `Formatable` the span can be serialized to
/// a string. For deserialization from a string the `Parsable` trait must be implemented by `T`.
/// Support for `serde` is available when the `timespan` crate is configured with the
/// `with-serde` feature.
///
/// This type implements operations known from the set theory. However, there are only operations
/// allowed that produce a single span (e.g. the resulting span is continuous) that must not be
/// empty. When an operation would violate these restrictions an error is emitted.
///
/// > Developer note: A `Span` may accept all possible input values without leading to errors in
/// > the future by producing an iterator over the results allowing an arbitrary amount of
/// > resulting spans.
///
/// # Example
///
/// ~~~~
/// # extern crate timespan; extern crate chrono; fn main() {
/// use timespan::Span;
/// use chrono::NaiveTime;
///
/// let start = "12:30:00".parse().unwrap();
/// let end = "14:45:00".parse().unwrap();
/// let span: Span<NaiveTime> = Span::new(start, end).unwrap();
///
/// assert!(format!("{}", span) == "12:30:00 - 14:45:00");
/// # }
/// ~~~~
#[derive(PartialEq, Clone)]
pub struct Span<T> {
    /// The starting point of the span.
    pub start: T,
    /// The end point of the span.
    pub end: T,
}

impl<T> Span<T>
where
    T: Spanable,
{
    /// Create a new span with a given starting point and a given end point.
    ///
    /// This method emits an `Error::Ordering` error when the end point lies
    /// before the start point.
    pub fn new(start: T, end: T) -> Result<Span<T>, Error> {
        if start >= end {
            return Err(Error::Ordering);
        }

        Ok(Span {
            start: start,
            end: end,
        })
    }

    /// Get the total duration of the span as a `chrono::Duration`.
    pub fn duration(&self) -> Duration {
        self.end.signed_duration_since(self.start)
    }

    /// Calculate the mathematical difference of two spans with the same `Spanable` type.
    ///
    /// The difference of span `self` and `other` includes the parts of span `self` that are
    /// not included in span `other`.
    ///
    /// This method produces an error when
    ///
    ///  - the resulting difference would produce an empty span (`Error::Empty`)
    ///  - the resulting difference is not continuous (e.g. splitted) (`Error::NotContinuous`)
    ///
    pub fn difference(&self, other: &Span<T>) -> Result<Span<T>, Error> {
        if self.start >= other.start && self.end <= other.end {
            // -(--[-]--)- -> err
            return Err(Error::Empty);
        } else if self.end <= other.start {
            // -[##]-(--)-
            return Ok(self.clone());
        } else if self.start >= other.end {
            // -(--)-[##]-
            return Ok(self.clone());
        } else if self.end > other.start && self.end <= other.end && self.start < other.start {
            // -[##(-]--)-
            return Ok(Span {
                start: self.start,
                end: other.start,
            });
        } else if self.start >= other.start && self.start < other.end && self.end > other.end {
            // -(--[-)##]-
            return Ok(Span {
                start: other.end,
                end: self.end,
            });
        } else {
            // -[##(-)##]- -> err
            return Err(Error::NotContinuous);
        }
    }

    /// Calculate the mathematical symmetric difference of two spans with the same `Spanable` type.
    ///
    /// The symmetric difference of span `self` and `other` includes the parts of span `self` that
    /// are not included in span `other` and the parts of span `other` that are not included in span
    /// `self`.
    ///
    /// This method produces an error when the resulting symmetric difference is not continuous
    /// (e.g. splitted) (`Error::NotContinuous`). As this is only not the case when the two spans
    /// are adjacent this method will most likely produce an error.
    pub fn symmetric_difference(&self, other: &Span<T>) -> Result<Span<T>, Error> {
        if self.end == other.start {
            // -[##](##)-
            return Ok(Span {
                start: self.start,
                end: other.end,
            });
        } else if other.end == self.start {
            // -(##)[##]-
            return Ok(Span {
                start: other.start,
                end: self.end,
            });
        } else {
            return Err(Error::NotContinuous);
        }
    }

    /// Calculate the mathematical intersection of two spans with the same `Spanable` type.
    ///
    /// The intersection of span `self` and `other` includes the parts that are included in span `self`
    /// and span `other`.
    ///
    /// This method produces an `Error::Empty` error when there is no intersection between the
    /// two spans.
    pub fn intersection(&self, other: &Span<T>) -> Result<Span<T>, Error> {
        if self.end <= other.start || other.end <= self.start {
            Err(Error::Empty)
        } else {
            Ok(Span {
                start: std::cmp::max(self.start, other.start),
                end: std::cmp::min(self.end, other.end),
            })
        }
    }

    /// Calculate the mathematical union of two spans with the same `Spanable` type.
    ///
    /// The union of span `self` and `other` includes the parts that are included in span `self` or
    /// span `other`.
    ///
    /// This method produces an `Error::NotContinuous` error when the two spans are not intersecting
    /// or adjacent to each other.
    pub fn union(&self, other: &Span<T>) -> Result<Span<T>, Error> {
        if self.end < other.start || other.end < self.start {
            Err(Error::NotContinuous)
        } else {
            Ok(Span {
                start: std::cmp::min(self.start, other.start),
                end: std::cmp::max(self.end, other.end),
            })
        }
    }

    /// Returns `true` when a given `Spanable` is included in `self`. Otherwise returns `false`.
    pub fn contains(&self, item: &T) -> bool {
        self.start <= *item && self.end >= *item
    }

    /// Returns `true` when `self` has no parts in common with `other`. Otherwise returns `false`.
    pub fn is_disjoint(&self, other: &Span<T>) -> bool {
        self.end <= other.start || self.start >= other.end
    }

    /// Returns `true` when `self` is completely included in `other`. Otherwise returns `false`.
    pub fn is_subset(&self, other: &Span<T>) -> bool {
        self.start >= other.start && self.end <= other.end
    }

    /// Returns `true` when `other` is completely included in `self`. Otherwise returns `false`.
    pub fn is_superset(&self, other: &Span<T>) -> bool {
        self.start <= other.start && self.end >= other.end
    }

    /// Split `self` at a given time point `at` into two spans of the same `Spanable` type.
    ///
    /// This emits an `Error::OutOfRange` error when `at` is not included in `self`.
    pub fn split_off(&self, at: &T) -> Result<(Span<T>, Span<T>), Error> {
        if self.start >= *at || self.end <= *at {
            return Err(Error::OutOfRange);
        }

        Ok((
            Span {
                start: self.start,
                end: *at,
            },
            Span {
                start: *at,
                end: self.end,
            },
        ))
    }

    /// Move the end point forward in time by a given duration.
    ///
    /// This emits an `Error::Empty` error when the operation would produce an empty span
    /// (e.g. the duration is negative).
    pub fn append(&mut self, time: &Duration) -> Result<(), Error> {
        let new = self.end + *time;
        if new <= self.start {
            return Err(Error::Empty);
        }
        self.end = new;
        Ok(())
    }

    /// Move the start point backward in time by a given duration.
    ///
    /// This emits an `Error::Empty` error when the operation would produce an empty span.
    /// (e.g. the duration is negative).
    pub fn prepend(&mut self, time: &Duration) -> Result<(), Error> {
        let new = self.start - *time;
        if new >= self.end {
            return Err(Error::Empty);
        }
        self.start = new;
        Ok(())
    }

    /// Move the end point backward in time by a given duration.
    ///
    /// This emits an `Error::Empty` error when the operation would produce an empty span.
    pub fn pop(&mut self, time: &Duration) -> Result<(), Error> {
        let new = self.end - *time;
        if new <= self.start {
            return Err(Error::Empty);
        }
        self.end = new;
        Ok(())
    }

    /// Move the start point forward in time by a given duration.
    ///
    /// This emits an `Error::Empty` error when the operation would produce an empty span.
    pub fn shift(&mut self, time: &Duration) -> Result<(), Error> {
        let new = self.start + *time;
        if new >= self.end {
            return Err(Error::Empty);
        }
        self.start = new;
        Ok(())
    }
}

impl<T> Span<T>
where
    T: Spanable + Formatable,
{
    /// Formats the span with the specified format strings.
    ///
    /// For the `start` and `end` format strings see the `chrono::format::strftime` module.
    ///
    /// The `fmt` string is used to format the span to a string. It must contain the following
    /// substrings:
    ///
    ///  - `{start}` to match the `start` point of the span
    ///  - `{end}` to match the `end` point of the span
    ///
    /// # Example
    ///
    /// ~~~~
    /// # extern crate timespan; fn main() {
    /// use timespan::NaiveTimeSpan;
    ///
    /// let span: NaiveTimeSpan = "12:30:00 - 14:45:00".parse().unwrap();
    ///
    /// let f = span.format("from {start} to {end}", "%H.%M", "%H.%M");
    /// assert!(f.to_string() == "from 12.30 to 14.45");
    /// assert!(format!("{}", f) == "from 12.30 to 14.45");
    /// # }
    /// ~~~~
    pub fn format<'a>(&self, fmt: &'a str, start: &'a str, end: &'a str) -> DelayedFormat<'a, T> {
        DelayedFormat {
            span: self.clone(),
            fmt: fmt,
            start: start,
            end: end,
        }
    }
}

impl<T> Span<T>
where
    T: Spanable + Parsable,
{
    /// Parses the span with the specified format strings from a given string `s`.
    ///
    /// For the `start` and `end` format strings see the `chrono::format::strftime` module.
    ///
    /// The `fmt` string is used to parse a span from a string. It must contain the following
    /// substrings:
    ///
    ///  - `{start}` to match the `start` point of the span
    ///  - `{end}` to match the `end` point of the span
    ///
    /// # Example
    /// ~~~~
    /// # extern crate timespan; fn main() {
    /// use timespan::NaiveTimeSpan;
    ///
    /// let span = NaiveTimeSpan::parse_from_str(
    ///     "from 12.30 to 14.45",
    ///     "from {start} to {end}",
    ///     "%H.%M",
    ///     "%H.%M",
    /// ).unwrap();
    ///
    /// assert!(format!("{}", span) == "12:30:00 - 14:45:00");
    /// # }
    /// ~~~~
    pub fn parse_from_str(s: &str, fmt: &str, start: &str, end: &str) -> Result<Span<T>, Error> {
        let esc = regex::escape(fmt);

        let repl_re = Regex::new(r"(?:\\\{start\\\}|\\\{end\\\})").unwrap();
        let repl = repl_re.replace_all(&esc, r"(.*)");

        let re = Regex::new(&repl)?;
        let caps = re.captures(s).ok_or(Error::Empty)?;

        let start_idx = fmt.find("{start}").ok_or(Error::NoStart)?;
        let end_idx = fmt.find("{end}").ok_or(Error::NoEnd)?;

        // we already checked for the existance of {start} and {end} captures -> unwrap allowed
        let m1 = caps.get(1).unwrap();
        let m2 = caps.get(2).unwrap();

        if start_idx < end_idx {
            Span::new(
                T::parse_from_str(m1.as_str(), start)?,
                T::parse_from_str(m2.as_str(), end)?,
            )
        } else {
            Span::new(
                T::parse_from_str(m2.as_str(), start)?,
                T::parse_from_str(m1.as_str(), end)?,
            )
        }
    }
}

/// Parses a `Span` from a string in the format `{start} - {end}`.
impl<T> std::str::FromStr for Span<T>
where
    T: Spanable + Parsable,
{
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let re = Regex::new(r"(.*)\s+-\s+(.*)").unwrap();
        let caps = re.captures(s).ok_or(Error::Empty)?;

        let c1 = caps.get(1).ok_or(Error::NoStart)?;
        let c2 = caps.get(2).ok_or(Error::NoEnd)?;

        Span::new(T::from_str(c1.as_str())?, T::from_str(c2.as_str())?)
    }
}

/// Formats a `Span` in the format `{start} - {end}`.
impl<T> std::fmt::Debug for Span<T>
where
    T: Spanable + Formatable,
{
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{} - {}", self.start, self.end)
    }
}

/// Formats a `Span` in the format `{start} - {end}`.
impl<T> std::fmt::Display for Span<T>
where
    T: Spanable + Formatable,
{
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        std::fmt::Debug::fmt(self, f)
    }
}

#[cfg(feature = "with-serde")]
mod with_serde {
    use super::Formatable;
    use super::Parsable;
    use super::Span;
    use super::Spanable;
    use serde::{de, ser};
    use std::fmt;
    use std::marker::PhantomData;

    impl<T> ser::Serialize for Span<T>
    where
        T: Spanable + Formatable,
    {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: ser::Serializer,
        {
            serializer.collect_str(&self)
        }
    }

    struct SpanVisitor<T> {
        phantom: PhantomData<T>,
    }

    impl<'de, T> de::Visitor<'de> for SpanVisitor<T>
    where
        T: Spanable + Parsable,
    {
        type Value = Span<T>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "a formatted time span string")
        }

        fn visit_str<E>(self, value: &str) -> Result<Span<T>, E>
        where
            E: de::Error,
        {
            value.parse().map_err(|err| E::custom(format!("{}", err)))
        }
    }

    impl<'de, T> de::Deserialize<'de> for Span<T>
    where
        T: Spanable + Parsable,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: de::Deserializer<'de>,
        {
            deserializer.deserialize_str(SpanVisitor {
                phantom: PhantomData,
            })
        }
    }
}
