Documentation
// Copyright 2019 YechaoLi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::IsNone;
use regex::Regex;
use std::borrow::Cow;

lazy_static! {
    static ref CONTAINS_WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
}

impl IsNone for String {
    fn is_none(&self) -> bool {
        self.is_empty()
    }
}

impl<'a> IsNone for Cow<'a, str> {
    fn is_none(&self) -> bool {
        self.is_empty()
    }
}

pub trait PruneTrim {
    fn prune_trim(self) -> Self;
}

pub trait PruneTrimStart {
    fn prune_trim_start(self) -> Self;
}

pub trait PruneTrimEnd {
    fn prune_trim_end(self) -> Self;
}

pub trait PruneNoWhitespace {
    fn prune_no_whitespace(self) -> Self;
}

impl PruneTrim for String {
    fn prune_trim(self) -> Self {
        let s = self.trim();
        if s != self {
            return s.to_owned();
        }
        self
    }
}

impl<'a> PruneTrim for Cow<'a, str> {
    fn prune_trim(self) -> Self {
        let s = self.trim();
        if s != self {
            return Cow::Owned(s.to_owned());
        }
        self
    }
}

impl<T> PruneTrim for Option<T>
where
    T: PruneTrim + IsNone,
{
    fn prune_trim(self) -> Self {
        match self {
            None => None,
            Some(s) => {
                let s = s.prune_trim();
                if s.is_none() {
                    None
                } else {
                    Some(s)
                }
            }
        }
    }
}

impl<T> PruneTrim for Vec<T>
where
    T: PruneTrim,
{
    fn prune_trim(self) -> Self {
        self.into_iter().map(PruneTrim::prune_trim).collect()
    }
}

impl PruneTrimStart for String {
    fn prune_trim_start(self) -> Self {
        let s = self.trim_start();
        if s != self {
            return s.to_owned();
        }
        self
    }
}

impl<'a> PruneTrimStart for Cow<'a, str> {
    fn prune_trim_start(self) -> Self {
        let s = self.trim_start();
        if s != self {
            return Cow::Owned(s.to_owned());
        }
        self
    }
}

impl<T> PruneTrimStart for Option<T>
where
    T: PruneTrimStart + IsNone,
{
    fn prune_trim_start(self) -> Self {
        match self {
            None => None,
            Some(s) => {
                let s = s.prune_trim_start();
                if s.is_none() {
                    None
                } else {
                    Some(s)
                }
            }
        }
    }
}

impl<T> PruneTrimStart for Vec<T>
where
    T: PruneTrimStart,
{
    fn prune_trim_start(self) -> Self {
        self.into_iter()
            .map(PruneTrimStart::prune_trim_start)
            .collect()
    }
}

impl PruneTrimEnd for String {
    fn prune_trim_end(self) -> Self {
        let s = self.trim_end();
        if s != self {
            return s.to_owned();
        }
        self
    }
}

impl<'a> PruneTrimEnd for Cow<'a, str> {
    fn prune_trim_end(self) -> Self {
        let s = self.trim_end();
        if s != self {
            return Cow::Owned(s.to_owned());
        }
        self
    }
}

impl<T> PruneTrimEnd for Option<T>
where
    T: PruneTrimEnd + IsNone,
{
    fn prune_trim_end(self) -> Self {
        match self {
            None => None,
            Some(s) => {
                let s = s.prune_trim_end();
                if s.is_none() {
                    None
                } else {
                    Some(s)
                }
            }
        }
    }
}

impl<T> PruneTrimEnd for Vec<T>
where
    T: PruneTrimEnd,
{
    fn prune_trim_end(self) -> Self {
        self.into_iter().map(PruneTrimEnd::prune_trim_end).collect()
    }
}

impl PruneNoWhitespace for String {
    fn prune_no_whitespace(self) -> Self {
        let s = CONTAINS_WHITESPACE.replace_all(&self, "");
        if s != self {
            return s.into_owned();
        }
        self
    }
}

impl<'a> PruneNoWhitespace for Cow<'a, str> {
    fn prune_no_whitespace(self) -> Self {
        let s = CONTAINS_WHITESPACE.replace_all(&self, "");
        if s != self {
            return Cow::Owned(s.into_owned());
        }
        self
    }
}

impl<T> PruneNoWhitespace for Option<T>
where
    T: PruneNoWhitespace + IsNone,
{
    fn prune_no_whitespace(self) -> Self {
        match self {
            None => None,
            Some(s) => {
                let s = s.prune_no_whitespace();
                if s.is_none() {
                    None
                } else {
                    Some(s)
                }
            }
        }
    }
}

impl<T> PruneNoWhitespace for Vec<T>
where
    T: PruneNoWhitespace,
{
    fn prune_no_whitespace(self) -> Self {
        self.into_iter()
            .map(PruneNoWhitespace::prune_no_whitespace)
            .collect()
    }
}

macro_rules! tuple_impls {
    ($(
        ($(($idx:tt)),+)
    )+) => {
        tuple_impls! {
            $(
                ($((T, $idx)),+)
            )+
        }
    };
    ($(
        ($(($T:ident, $idx:tt)),+)
    )+) => {
        $(
            impl<T> PruneTrim for ($($T),+) where T: PruneTrim {
                fn prune_trim(self) -> Self {
                    (
                        $(self.$idx.prune_trim()),+
                    )
                }
            }

            impl<T> PruneTrimStart for ($($T),+) where T: PruneTrimStart {
                fn prune_trim_start(self) -> Self {
                    (
                        $(self.$idx.prune_trim_start()),+
                    )
                }
            }

            impl<T> PruneTrimEnd for ($($T),+) where T: PruneTrimEnd {
                fn prune_trim_end(self) -> Self {
                    (
                        $(self.$idx.prune_trim_end()),+
                    )
                }
            }

            impl<T> PruneNoWhitespace for ($($T),+) where T: PruneNoWhitespace {
                fn prune_no_whitespace(self) -> Self {
                    (
                        $(self.$idx.prune_no_whitespace()),+
                    )
                }
            }
        )+
    };
}

tuple_impls! {
    ((0), (1))
    ((0), (1), (2))
    ((0), (1), (2), (3))
    ((0), (1), (2), (3), (4))
    ((0), (1), (2), (3), (4), (5))
    ((0), (1), (2), (3), (4), (5), (6))
    ((0), (1), (2), (3), (4), (5), (6), (7))
    ((0), (1), (2), (3), (4), (5), (6), (7), (8))
    ((0), (1), (2), (3), (4), (5), (6), (7), (8), (9))
    ((0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10))
    ((0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11))
    ((0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12))
}

#[cfg(test)]
mod tests {
    extern crate test;

    #[bench]
    fn bench_prune_trim(b: &mut test::Bencher) {
        use super::PruneTrim;

        b.iter(|| {
            "  \n    \n tirm some thing  \n  \t   "
                .to_owned()
                .prune_trim();
        });

        b.iter(|| {
            "tirm some thing".to_owned().prune_trim();
        });
    }
}