use self::color::*;
use std::{
    borrow::Cow,
    fmt::{self, Display},
};
use termion::{
    color::{Bg, Color, Fg, Rgb},
    style::{Bold, Faint, Italic, Reset, Underline},
    terminal_size,
};

pub struct Termion;
pub struct Plain;

#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum Item {
    Head,
    Bar(Line),
    Arrow(Line),
    Version,
    Description,
    Author,
    Help,
    Group,
    Verb,
    Type,
    Option,
    Usage,
    Note,
    Link,
    Summary,
    Tag,
}

#[derive(Clone, Copy)]
pub enum Line {
    Head,
    Description,
    Link,
    Usage,
}

pub trait Format {
    fn width(&self) -> usize;
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result;
}

macro_rules! format {
    ($type: ty, $count: expr) => {
        impl Format for $type {
            #[inline]
            fn width(&self) -> usize {
                $count
            }

            #[inline]
            fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                fmt::Display::fmt(self, formatter)
            }
        }
    };
}

macro_rules! tuple {
    ($or: ident $(, $name: ident, $index: tt)*) => {
        impl<$($name: Format),*> Format for ($($name,)*) {
            #[inline]
            fn width(&self) -> usize {
                $(self.$index.width() + )* 0
            }

            #[inline]
            fn format(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
                $(self.$index.format(_formatter)?;)*
                Ok(())
            }
        }

        impl<$($name: Format),*> Format for orn::$or<$($name,)*> {
            #[inline]
            fn width(&self) -> usize {
                match self {
                    $(orn::$or::$name(value) => value.width(),)*
                    #[allow(unreachable_patterns)]
                    _ => 0,
                }
            }

            #[inline]
            fn format(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
                match self {
                    $(orn::$or::$name(value) => value.format(_formatter),)*
                    #[allow(unreachable_patterns)]
                    _ => Ok(()),
                }
            }
        }
    };
}

tuple!(Or0);
tuple!(Or1, T0, 0);
tuple!(Or2, T0, 0, T1, 1);
tuple!(Or3, T0, 0, T1, 1, T2, 2);
tuple!(Or4, T0, 0, T1, 1, T2, 2, T3, 3);
tuple!(Or5, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4);
tuple!(Or6, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5);
tuple!(Or7, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5, T6, 6);
tuple!(Or8, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5, T6, 6, T7, 7);
tuple!(Or9, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5, T6, 6, T7, 7, T8, 8);
tuple!(Or10, T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5, T6, 6, T7, 7, T8, 8, T9, 9);

impl<T: Format + ?Sized> Format for &T {
    #[inline]
    fn width(&self) -> usize {
        T::width(self)
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        T::format(self, formatter)
    }
}

impl<T: Format> Format for [T] {
    #[inline]
    fn width(&self) -> usize {
        self.iter().map(Format::width).sum()
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        for item in self {
            item.format(formatter)?;
        }
        Ok(())
    }
}

impl<T: Format, const N: usize> Format for [T; N] {
    #[inline]
    fn width(&self) -> usize {
        self.iter().map(Format::width).sum()
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        for item in self {
            item.format(formatter)?;
        }
        Ok(())
    }
}

impl Format for Cow<'_, str> {
    #[inline]
    fn width(&self) -> usize {
        self.chars().count()
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(self)
    }
}

impl Format for String {
    #[inline]
    fn width(&self) -> usize {
        self.chars().count()
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(self)
    }
}

impl Format for str {
    #[inline]
    fn width(&self) -> usize {
        self.chars().count()
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(self)
    }
}

impl<C: Color> Format for Fg<C> {
    #[inline]
    fn width(&self) -> usize {
        0
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.fmt(formatter)
    }
}

impl<C: Color> Format for Bg<C> {
    #[inline]
    fn width(&self) -> usize {
        0
    }

    #[inline]
    fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.fmt(formatter)
    }
}

format!(char, 1);
format!(Reset, 0);
format!(Bold, 0);
format!(Faint, 0);
format!(Italic, 0);
format!(Underline, 0);

pub trait Style {
    fn indent(&self) -> usize;
    fn width(&self) -> usize;
    fn begin(&self, item: Item) -> &dyn Format;
    fn end(&self, item: Item) -> &dyn Format;
}

macro_rules! dynamic {
    ($($value: expr),*) => {
        &[$(&$value as &dyn Format),*]
    };
}
impl Style for Termion {
    #[inline]
    fn indent(&self) -> usize {
        2
    }

    #[inline]
    fn width(&self) -> usize {
        terminal_size().map_or(64, |pair| pair.0 as usize - 32)
    }

    #[inline]
    fn begin(&self, item: Item) -> &dyn Format {
        const BAR: char = '│';
        const ARROW: char = '>';
        const HEAD: Fg<Rgb> = Fg(RUBY_RED);
        const DESCRIPTION: Fg<Rgb> = Fg(SALMON_PINK);
        const LINK: Fg<Rgb> = Fg(SALMON_PINK);
        const USAGE: Fg<Rgb> = Fg(VIOLET);

        match item {
            Item::Head => dynamic!(Bold, HEAD),
            Item::Bar(Line::Head) => dynamic!(HEAD, BAR),
            Item::Bar(Line::Description) => dynamic!(DESCRIPTION, BAR),
            Item::Bar(Line::Link) => dynamic!(LINK, BAR),
            Item::Bar(Line::Usage) => dynamic!(USAGE, BAR),
            Item::Arrow(Line::Head) => dynamic!(HEAD, ARROW),
            Item::Arrow(Line::Description) => dynamic!(DESCRIPTION, ARROW),
            Item::Arrow(Line::Link) => dynamic!(LINK, ARROW),
            Item::Arrow(Line::Usage) => dynamic!(USAGE, ARROW),
            Item::Version => dynamic!(Bold, Fg(RUBY_RED)),
            Item::Author => dynamic!(Faint, Italic, HEAD),
            Item::Description => dynamic!(DESCRIPTION),
            Item::Help => dynamic!(Fg(PEACH)),
            Item::Group => dynamic!(Bold, Fg(OCEAN_BLUE)),
            Item::Verb => dynamic!(Bold, Fg(TURQUOISE)),
            Item::Option => dynamic!(Fg(TURQUOISE)),
            Item::Type => dynamic!(Faint, Fg(TURQUOISE), '<'),
            Item::Usage => dynamic!(Underline, USAGE),
            Item::Link => dynamic!(Italic, LINK),
            Item::Note => dynamic!(Italic, Fg(SILVER_GRAY)),
            Item::Summary => dynamic!(Fg(SANDY_BROWN)),
            Item::Tag => dynamic!(Faint, Fg(CORAL_PINK), '['),
        }
    }

    #[inline]
    fn end(&self, item: Item) -> &dyn Format {
        match item {
            Item::Tag => dynamic!(']', Reset),
            Item::Type => dynamic!('>', Reset),
            _ => dynamic!(Reset),
        }
    }
}

impl Style for Plain {
    #[inline]
    fn indent(&self) -> usize {
        2
    }

    #[inline]
    fn width(&self) -> usize {
        96
    }

    #[inline]
    fn begin(&self, item: Item) -> &dyn Format {
        const BAR: char = '~';
        const ARROW: &str = "  ";

        match item {
            Item::Bar(Line::Head) => dynamic!(BAR),
            Item::Bar(Line::Description) => dynamic!(BAR),
            Item::Bar(Line::Link) => dynamic!(BAR),
            Item::Bar(Line::Usage) => dynamic!(BAR),
            Item::Arrow(Line::Head) => dynamic!(ARROW),
            Item::Arrow(Line::Description) => dynamic!(ARROW),
            Item::Arrow(Line::Link) => dynamic!(ARROW),
            Item::Arrow(Line::Usage) => dynamic!(ARROW),
            Item::Type => dynamic!('<'),
            Item::Tag => dynamic!('['),
            _ => dynamic!(""),
        }
    }

    #[inline]
    fn end(&self, item: Item) -> &dyn Format {
        match item {
            Item::Tag => dynamic!(']'),
            Item::Type => dynamic!('>'),
            _ => dynamic!(""),
        }
    }
}

pub mod color {
    use super::*;

    pub const OCEAN_BLUE: Rgb = Rgb(36, 113, 163);
    pub const TURQUOISE: Rgb = Rgb(64, 224, 208);
    pub const RUBY_RED: Rgb = Rgb(220, 20, 60);
    pub const SILVER_GRAY: Rgb = Rgb(169, 169, 169);
    pub const CORAL_PINK: Rgb = Rgb(255, 127, 80);
    pub const VIOLET: Rgb = Rgb(238, 130, 238);
    pub const PEACH: Rgb = Rgb(255, 218, 185);
    pub const SALMON_PINK: Rgb = Rgb(255, 145, 164);
    pub const SANDY_BROWN: Rgb = Rgb(244, 164, 96);
    pub const SUNFLOWER_YELLOW: Rgb = Rgb(255, 255, 85);
    pub const MANGO_ORANGE: Rgb = Rgb(255, 179, 71);
    pub const SEAFOAM_GREEN: Rgb = Rgb(50, 205, 153);
    pub const COBALT_BLUE: Rgb = Rgb(0, 71, 171);
    pub const SLATE_GRAY: Rgb = Rgb(112, 128, 144);
    pub const LAVENDER: Rgb = Rgb(230, 230, 250);
    pub const BURGUNDY: Rgb = Rgb(128, 0, 32);
    pub const SUNSET_ORANGE: Rgb = Rgb(255, 140, 79);
    pub const GOLDEN_YELLOW: Rgb = Rgb(255, 223, 0);
    pub const SKY_BLUE: Rgb = Rgb(135, 206, 250);
    pub const EARTH_BROWN: Rgb = Rgb(139, 69, 19);
    pub const MINT_GREEN: Rgb = Rgb(152, 255, 152);
    pub const MAUVE: Rgb = Rgb(224, 176, 255);
    pub const EMERALD_GREEN: Rgb = Rgb(0, 158, 96);
    pub const AMETHYST_PURPLE: Rgb = Rgb(138, 43, 226);
    pub const GOLDENROD_YELLOW: Rgb = Rgb(218, 165, 32);
    pub const LIME_GREEN: Rgb = Rgb(50, 205, 50);
    pub const TEAL: Rgb = Rgb(0, 128, 128);
    pub const SAFFRON_YELLOW: Rgb = Rgb(244, 196, 48);
    pub const INDIGO: Rgb = Rgb(75, 0, 130);
    pub const AQUA: Rgb = Rgb(0, 255, 255);
    pub const FOREST_GREEN: Rgb = Rgb(34, 139, 34);
    pub const STEEL_BLUE: Rgb = Rgb(70, 130, 180);
    pub const CHOCOLATE_BROWN: Rgb = Rgb(139, 69, 19);
    pub const CORNFLOWER_BLUE: Rgb = Rgb(100, 149, 237);
    pub const OLIVE_GREEN: Rgb = Rgb(128, 128, 0);
}
