//! ## Bytes
//!
//! `Bytes` component extends an `Input` component in order to provide an input type for byte size.

/**
 * MIT License
 *
 * termscp - Copyright (c) 2021 Christian Visintin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
// locals
use crate::utils::fmt::fmt_bytes;
use crate::utils::parser::parse_bytesize;
// ext
use tui_realm_stdlib::{Input, InputPropsBuilder};
use tuirealm::event::Event;
use tuirealm::props::{Alignment, Props, PropsBuilder};
use tuirealm::tui::{
    layout::Rect,
    style::Color,
    widgets::{BorderType, Borders},
};
use tuirealm::{Component, Frame, Msg, Payload, Value};

// -- props

/// ## BytesPropsBuilder
///
/// A wrapper around an `InputPropsBuilder`
pub struct BytesPropsBuilder {
    puppet: InputPropsBuilder,
}

impl Default for BytesPropsBuilder {
    fn default() -> Self {
        Self {
            puppet: InputPropsBuilder::default(),
        }
    }
}

impl PropsBuilder for BytesPropsBuilder {
    fn build(&mut self) -> Props {
        self.puppet.build()
    }

    fn hidden(&mut self) -> &mut Self {
        self.puppet.hidden();
        self
    }

    fn visible(&mut self) -> &mut Self {
        self.puppet.visible();
        self
    }
}

impl From<Props> for BytesPropsBuilder {
    fn from(props: Props) -> Self {
        BytesPropsBuilder {
            puppet: InputPropsBuilder::from(props),
        }
    }
}

impl BytesPropsBuilder {
    /// ### with_borders
    ///
    /// Set component borders style
    pub fn with_borders(
        &mut self,
        borders: Borders,
        variant: BorderType,
        color: Color,
    ) -> &mut Self {
        self.puppet.with_borders(borders, variant, color);
        self
    }

    /// ### with_label
    ///
    /// Set input label
    pub fn with_label<S: AsRef<str>>(&mut self, label: S, alignment: Alignment) -> &mut Self {
        self.puppet.with_label(label, alignment);
        self
    }

    /// ### with_color
    ///
    /// Set initial value for component
    pub fn with_foreground(&mut self, color: Color) -> &mut Self {
        self.puppet.with_foreground(color);
        self
    }

    /// ### with_color
    ///
    /// Set initial value for component
    pub fn with_value(&mut self, val: u64) -> &mut Self {
        self.puppet.with_value(fmt_bytes(val));
        self
    }
}

// -- component

/// ## Bytes
///
/// a wrapper component of `Input` which adds a superset of rules to behave as a color picker
pub struct Bytes {
    input: Input,
    native_color: Color,
}

impl Bytes {
    /// ### new
    ///
    /// Instantiate a new `Bytes`
    pub fn new(props: Props) -> Self {
        // Instantiate a new color picker using input
        Self {
            native_color: props.foreground,
            input: Input::new(props),
        }
    }

    /// ### update_colors
    ///
    /// Update colors to match selected color, with provided one
    fn update_colors(&mut self, color: Color) {
        let mut props = self.get_props();
        props.foreground = color;
        props.borders.color = color;
        let _ = self.input.update(props);
    }
}

impl Component for Bytes {
    /// ### render
    ///
    /// Based on the current properties and states, renders a widget using the provided render engine in the provided Area
    /// If focused, cursor is also set (if supported by widget)
    #[cfg(not(tarpaulin_include))]
    fn render(&self, render: &mut Frame, area: Rect) {
        self.input.render(render, area);
    }

    /// ### update
    ///
    /// Update component properties
    /// Properties should first be retrieved through `get_props` which creates a builder from
    /// existing properties and then edited before calling update.
    /// Returns a Msg to the view
    fn update(&mut self, props: Props) -> Msg {
        let msg: Msg = self.input.update(props);
        match msg {
            Msg::OnChange(Payload::One(Value::Str(input))) => {
                match parse_bytesize(input.as_str()) {
                    Some(bytes) => {
                        // return OK
                        self.update_colors(self.native_color);
                        Msg::OnChange(Payload::One(Value::U64(bytes.as_u64())))
                    }
                    None => {
                        // Invalid color
                        self.update_colors(Color::Red);
                        Msg::None
                    }
                }
            }
            msg => msg,
        }
    }

    /// ### get_props
    ///
    /// Returns a props builder starting from component properties.
    /// This returns a prop builder in order to make easier to create
    /// new properties for the element.
    fn get_props(&self) -> Props {
        self.input.get_props()
    }

    /// ### on
    ///
    /// Handle input event and update internal states.
    /// Returns a Msg to the view
    fn on(&mut self, ev: Event) -> Msg {
        // Capture message from input
        match self.input.on(ev) {
            Msg::OnChange(Payload::One(Value::Str(input))) => {
                // Capture color and validate
                match parse_bytesize(input.as_str()) {
                    Some(bytes) => {
                        // Update color and return OK
                        self.update_colors(self.native_color);
                        Msg::OnChange(Payload::One(Value::U64(bytes.as_u64())))
                    }
                    None => {
                        // Invalid color
                        self.update_colors(Color::Red);
                        Msg::None
                    }
                }
            }
            Msg::OnSubmit(_) => Msg::None,
            msg => msg,
        }
    }

    /// ### get_state
    ///
    /// Get current state from component
    /// For this component returns Unsigned if the input type is a number, otherwise a text
    /// The value is always the current input.
    fn get_state(&self) -> Payload {
        match self.input.get_state() {
            Payload::One(Value::Str(bytes)) => match parse_bytesize(bytes.as_str()) {
                None => Payload::None,
                Some(bytes) => Payload::One(Value::U64(bytes.as_u64())),
            },
            _ => Payload::None,
        }
    }

    // -- events

    /// ### blur
    ///
    /// Blur component; basically remove focus
    fn blur(&mut self) {
        self.input.blur();
    }

    /// ### active
    ///
    /// Active component; basically give focus
    fn active(&mut self) {
        self.input.active();
    }
}

#[cfg(test)]
mod test {
    use super::*;

    use crossterm::event::{KeyCode, KeyEvent};
    use pretty_assertions::assert_eq;

    #[test]
    fn bytes_input() {
        let mut component: Bytes = Bytes::new(
            BytesPropsBuilder::default()
                .visible()
                .with_value(1024)
                .with_borders(Borders::ALL, BorderType::Double, Color::Rgb(204, 170, 0))
                .with_label("omar", Alignment::Left)
                .with_foreground(Color::Red)
                .build(),
        );
        // Focus
        component.blur();
        component.active();
        // Get value
        assert_eq!(component.get_state(), Payload::One(Value::U64(1024)));
        // Set an invalid color
        let props = InputPropsBuilder::from(component.get_props())
            .with_value(String::from("#pippo1"))
            .hidden()
            .build();
        assert_eq!(component.update(props), Msg::None);
        assert_eq!(component.get_state(), Payload::None);
        // Reset color
        let props = BytesPropsBuilder::from(component.get_props())
            .with_value(111)
            .hidden()
            .build();
        assert_eq!(
            component.update(props),
            Msg::OnChange(Payload::One(Value::U64(111)))
        );
        // Backspace (invalid)
        assert_eq!(
            component.on(Event::Key(KeyEvent::from(KeyCode::Backspace))),
            Msg::None
        );
        // Press '1'
        assert_eq!(
            component.on(Event::Key(KeyEvent::from(KeyCode::Char('B')))),
            Msg::OnChange(Payload::One(Value::U64(111)))
        );
    }
}
