1. 4FIPS
  2. PHOTOS
  3. VIDEOS
  4. APPS
  5. CODE
  6. FORUMS
  7. ABOUT
// [NUBCO BY FIPS @ 4FIPS.COM, (c) 2021 FILIP STOKLAS, MIT-LICENSED]

use eframe::{egui, epi};
use std::mem::size_of;
use crate::conv::*;
use urlencoding::encode as encode_url;

#[derive(Clone, Copy, Debug, PartialEq)]
enum Base {
    Hex = 16,
    Dec = 10,
    Oct = 8,
    Bin = 2,
}

struct ConversionTexts {
  uhex: ConversionText,
  udec: ConversionText,
  idec: ConversionText,
  uoct: ConversionText,
  ubin: ConversionText,
}

#[derive(Default)]
struct ConversionTargets {
  uhex: String,
  udec: String,
  idec: String,
  uoct: String,
  ubin: String,
}

#[derive(Default)]
struct State {
  targets8: ConversionTargets,
  targets16: ConversionTargets,
  targets32: ConversionTargets,
  targets64: ConversionTargets,
  targets128: ConversionTargets,
}

pub struct NubcoApp {
    app_name: String,
    is_first_update: bool,

    input: String,
    base: Base,

    state: State,
}

impl Default for NubcoApp {
    fn default() -> Self {
        Self {
            app_name: format!("n u b c o (apps.4fips.com/nubco) v{}", env!("CARGO_PKG_VERSION")),
            is_first_update: true,

            input: "beef".to_owned(),
            base: Base::Hex,

            state: State::default(),
        }
    }
}

impl epi::App for NubcoApp {
    fn name(&self) -> &str {
        &self.app_name
    }

    fn setup(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>, _storage: Option<&dyn epi::Storage>) {
        ctx.set_visuals(egui::Visuals::dark());
        let scale = if frame.is_web() { 1.5 } else { 1.0 };
        setup_style(ctx, scale);
        setup_fonts(ctx, scale);
    }

    fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
        let NubcoApp {
            app_name: _,
            is_first_update,

            input,
            base,

            state,
        } = self;

        egui::CentralPanel::default().show(ctx, |ui| {
            egui::ScrollArea::vertical().show(ui, |ui| {
                egui::Frame::default()
                .margin(egui::vec2(2.0, 2.0))
                .show(ui, |ui| {
                    about_panel(ui);
                    //debug_panel(ui, input, base);
                    input_panel(ui, input, base, *is_first_update);
                    conversion_panel(ui, input, base, state);
                });
            });
            *is_first_update = false;
        });
    }
}

fn input_panel(ui: &mut egui::Ui, input: &mut String, base: &mut Base, grab_focus: bool) {
    ui.add_space(6.0);

    ui.horizontal(|ui| {
        ui.add(egui::Label::new("Input:").heading().strong());
        let color = if ui.visuals().dark_mode { egui::Color32::YELLOW } else { egui::Color32::RED };
        let response = ui.add(egui::TextEdit::singleline(input).text_color(color).text_style(egui::TextStyle::Heading).desired_width(f32::INFINITY));
        if grab_focus {
            ui.memory().request_focus(response.id);
        }
    });

    ui.horizontal(|ui| {
        ui.label("Base:");
        ui.radio_value(base, Base::Hex, "hex");
        ui.radio_value(base, Base::Dec, "dec");
        ui.radio_value(base, Base::Oct, "oct");
        ui.radio_value(base, Base::Bin, "bin");
    });

    ui.add_space(6.0);
}

fn conversion_panel(ui: &mut egui::Ui, input: &mut String, base: &mut Base, state: &mut State) {
    let base = *base as u32;
    let trimmed_input = input.trim();

    if trimmed_input.is_empty() {
        return;
    }

    #[derive(Default)]
    struct Folding {
        is_last_item: bool,
        ok_unfolded: bool,
    }

    let mut folding = Folding::default();

    macro_rules! conversion_item {
        ($ui: expr, $U: ty, $I: ty, $value: expr, $base: expr, $targets: expr, $folding: expr) => {
            let ures = <$U>::from_str_radix($value, $base);
            let ires = <$I>::from_str_radix($value, $base);
            let ok = ures.is_ok() && ires.is_ok();
            let warn = ures.is_ok() || ires.is_ok();
            let symb = if ok { '✅' } else if warn { '❗' } else { '❌' };
            let texts = ConversionTexts {
                uhex: ConversionText::from(convert_ui(&ures, &ires, &val_to_hex)),
                udec: ConversionText::from(convert_ui(&ures, &ires, &val_to_dec)),
                idec: ConversionText::from(convert_iu(&ires, &ures, &val_to_dec)),
                uoct: ConversionText::from(convert_ui(&ures, &ires, &val_to_oct)),
                ubin: ConversionText::from(convert_ui(&ures, &ires, &val_to_bin)),
            };
            let nbits = (8 * size_of::<$U>()).to_string();
            let expand = !$folding.ok_unfolded && (ok || warn || $folding.is_last_item);
            conversion_item($ui, texts, $targets, &nbits, symb, expand);
            $folding.ok_unfolded |= ok;
        };
    }

    conversion_item!(ui, u8, i8, trimmed_input, base, &mut state.targets8, &mut folding);
    conversion_item!(ui, u16, i16, trimmed_input, base, &mut state.targets16, &mut folding);
    conversion_item!(ui, u32, i32, trimmed_input, base, &mut state.targets32, &mut folding);
    conversion_item!(ui, u64, i64, trimmed_input, base, &mut state.targets64, &mut folding);
    folding.is_last_item = true;
    conversion_item!(ui, u128, i128, trimmed_input, base, &mut state.targets128, &mut folding);
}

fn conversion_item(ui: &mut egui::Ui, texts: ConversionTexts, targets: &mut ConversionTargets, nbits: &str, symb: char, expand: bool) {
    egui::CollapsingHeader::new(format!("{} BIT {}{}", nbits, symb, if expand { " " } else { "" }))
    .default_open(expand)
    .show(ui, |ui| {
        egui::Grid::new(format!("grid_{}", nbits))
        .num_columns(2)
        .show(ui, |ui| {
            ui.label(format!("UINT{} (hex):", nbits));
            targets.uhex = texts.uhex.text;
            ui.add(egui::TextEdit::singleline(&mut targets.uhex).desired_width(ui.available_width() - 60.0));
            if texts.uhex.symb.is_empty() { ui.label(texts.uhex.symb) } else { ui.label(texts.uhex.symb).on_hover_text(texts.uhex.symb_hover) };
            ui.end_row();

            ui.label(format!("UINT{} (dec):", nbits));
            targets.udec = texts.udec.text;
            ui.add(egui::TextEdit::singleline(&mut targets.udec).desired_width(ui.available_width() - 60.0));
            if texts.udec.symb.is_empty() { ui.label(texts.udec.symb) } else { ui.label(texts.udec.symb).on_hover_text(texts.udec.symb_hover) };
            ui.end_row();

            ui.label(format!("INT{} (dec):", nbits));
            targets.idec = texts.idec.text;
            ui.add(egui::TextEdit::singleline(&mut targets.idec).desired_width(ui.available_width() - 60.0));
            if texts.idec.symb.is_empty() { ui.label(texts.idec.symb) } else { ui.label(texts.idec.symb).on_hover_text(texts.idec.symb_hover) };
            ui.end_row();

            ui.label(format!("UINT{} (oct):", nbits));
            targets.uoct = texts.uoct.text;
            ui.add(egui::TextEdit::singleline(&mut targets.uoct).desired_width(ui.available_width() - 60.0));
            if texts.uoct.symb.is_empty() { ui.label(texts.uoct.symb) } else { ui.label(texts.uoct.symb).on_hover_text(texts.uoct.symb_hover) };
            ui.end_row();

            ui.label(format!("UINT{} (bin):", nbits));
            targets.ubin = texts.ubin.text;
            ui.add(egui::TextEdit::singleline(&mut targets.ubin).desired_width(ui.available_width() - 60.0));
            if texts.ubin.symb.is_empty() { ui.label(texts.ubin.symb) } else { ui.label(texts.ubin.symb).on_hover_text(texts.ubin.symb_hover) };
            ui.end_row();
        });
    });
}

fn about_panel(ui: &mut egui::Ui) {
    egui::CollapsingHeader::new("About")
    .default_open(false)
    .show(ui, |ui| {
        ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
            let nubco_url = "https://apps.4fips.com/nubco";
            ui.add(egui::Label::new(format!("n u b c o   v{}", env!("CARGO_PKG_VERSION"))).heading().strong());
            ui.label("a numeric base converter");
            ui.label("brought to you for free by fips");
            ui.label("(c) 2021 filip stoklas");
            ui.hyperlink_to("apps.4fips.com/nubco", nubco_url);

            ui.add_space(12.0);
            ui.label("powered by:");
            ui.hyperlink_to("Rust", "https://www.rust-lang.org");
            ui.hyperlink_to("egui", "https://github.com/emilk/egui");

            ui.add_space(12.0);
            ui.label("support this app by sharing it on:");
            let share_text = "n u b c o ⭐ a numeric base converter ⭐ brought to you for free by fips ⭐ powered by Rust & egui";
            let share_text_with_tags = format!("{} ⭐ #rustlang #egui ⭐", share_text);
            ui.hyperlink_to("Twitter", share_on_twitter_url(nubco_url, &share_text_with_tags, "4fips"));
            ui.hyperlink_to("Facebook", share_on_facebook_url(nubco_url));
            ui.hyperlink_to("Reddit", share_on_reddit_url(nubco_url, share_text));
            ui.hyperlink_to("Hacker News", share_on_hackernews_url(nubco_url, share_text));
       });
    });
}

#[allow(dead_code)]
fn debug_panel(ui: &mut egui::Ui, input: &mut String, base: &mut Base) {
    egui::CollapsingHeader::new("Debug")
    .default_open(false)
    .show(ui, |ui| {
        ui.horizontal(|ui| {
            fn item(ui: &mut egui::Ui, input: &mut String, input_base: &mut Base, label: &str, value: &str, base: Base) {
                if ui.add(egui::Button::new(label)).clicked() {
                    *input = value.to_owned();
                    *input_base = base;
                }
            }
            ui.label("Debug:");
            item(ui, input, base, "8", "ff", Base::Hex);
            item(ui, input, base, "16", "ffff", Base::Hex);
            item(ui, input, base, "32", "ffffffff", Base::Hex);
            item(ui, input, base, "64", "ffffffffffffffff", Base::Hex);
            item(ui, input, base, "128", "ffffffffffffffffffffffffffffffff", Base::Hex);
            item(ui, input, base, "H1", "e00002d6", Base::Hex);
            item(ui, input, base, "H2", "0xe00002d6", Base::Hex);
            item(ui, input, base, "D1", "-536870186", Base::Dec);
            item(ui, input, base, "-1", "-1", Base::Dec);
        });
    });
}

fn share_on_twitter_url(url: &str, text: &str, via: &str) -> String {
    return format!("https://twitter.com/intent/tweet/?url={}&text={}&via={}", encode_url(url), encode_url(text), via);
}

fn share_on_facebook_url(url: &str) -> String {
    return format!("https://facebook.com/sharer/sharer.php?u={}", encode_url(url));
}

fn share_on_reddit_url(url: &str, text: &str) -> String {
    return format!("https://reddit.com/submit/?url={}&resubmit=true&title={}", encode_url(url), encode_url(text));
}

fn share_on_hackernews_url(url: &str, text: &str) -> String {
    return format!("https://news.ycombinator.com/submitlink?u={}&t={}", encode_url(url), encode_url(text));
}

fn setup_style(ctx: &egui::CtxRef, scale: f32) {
    let mut style = (*ctx.style()).clone();
    {
        macro_rules! corner_radius {
            ($varinat: ident, $value: expr) => {
                style.visuals.widgets.$varinat.corner_radius = $value;
            };
        }
        let radius = 6.0 * scale;
        corner_radius!(noninteractive, radius);
        corner_radius!(inactive, radius);
        corner_radius!(hovered, radius);
        corner_radius!(active, radius);
        corner_radius!(open, radius);
    }
    if scale > 1.0 {
        macro_rules! expansion {
            ($varinat: ident, $value: expr) => {
                style.visuals.widgets.$varinat.expansion = $value;
            };
        }
        let expansion = 5.0 * scale - 5.0;
        expansion!(noninteractive, expansion);
        expansion!(inactive, expansion);
        expansion!(hovered, expansion * 1.3);
        expansion!(active, expansion * 1.3);
        expansion!(open, expansion * 1.3);
        style.spacing.indent = 25.0;
    }
    style.spacing.item_spacing = egui::vec2(8.0 * scale, 8.0 * scale);
    style.visuals.collapsing_header_frame = true;
    //style.visuals.text_cursor_preview = true;
    //style.spacing.indent_ends_with_horizontal_line = true;
    ctx.set_style(style);
}

fn setup_fonts(ctx: &egui::CtxRef, scale: f32) {
    let mut fonts = egui::FontDefinitions::default();
    let font_size = 20.0 * scale;
    fonts.family_and_size.insert(egui::TextStyle::Heading, (egui::FontFamily::Proportional, font_size));
    fonts.family_and_size.insert(egui::TextStyle::Button, (egui::FontFamily::Proportional, font_size));
    fonts.family_and_size.insert(egui::TextStyle::Body, (egui::FontFamily::Proportional, font_size));
    ctx.set_fonts(fonts);
}