use anyhow::Result;
use chrono::{Timelike, Utc};
use colored::{
Color::{Blue, BrightBlack, Yellow},
Colorize,
};
use std::fmt::Write as _;
use crate::modules::{
localization::{WeatherCodeLocales, WeatherLocales},
units::{Precipitation, Temperature, Time, Units},
weather::Weather,
};
use super::{
border::*,
graph::{Graph, GraphOpts},
gui_config::{ColorOption, ColorVariant},
utils::style_number,
weathercode::WeatherCode,
};
const DISPLAY_HOURS: [usize; 8] = [0, 3, 6, 9, 12, 15, 18, 21];
pub struct HourlyForecast {
temperatures: String,
precipitation: String,
temp_max_min: String,
precipitation_probability_max: u8,
graph: Graph,
time_indicator_col: Option<usize>,
}
impl HourlyForecast {
pub fn render(
self,
width: usize,
units: &Units,
border_style: &BorderStyle,
color_variant: &ColorVariant,
t: &WeatherLocales,
) {
println!(
"{}",
&Separator::Blank
.fmt(width, border_style)
.color_option(BrightBlack, color_variant)
);
let temperature_unit = match units.temperature {
Temperature::fahrenheit => "",
_ => "",
};
let precipitation_unit = match units.precipitation {
Precipitation::mm => "ₘₘ",
Precipitation::inch => "ᵢₙ",
_ => "",
};
println!(
"{} {: <width$} {}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
t.hourly_forecast.bold(),
Border::R.fmt(border_style).color_option(BrightBlack, color_variant),
width = width - 2
);
println!(
"{} {} ❲{}{}❳{: <width$} {}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
self.temp_max_min,
self.precipitation_probability_max,
"".bold(),
"",
Border::R.fmt(border_style).color_option(BrightBlack, color_variant),
width = width - 5 - self.temp_max_min.len() - self.precipitation_probability_max.to_string().len()
);
match self.time_indicator_col {
Some(_) => {
println!(
"{}",
self.prepare_separator(border_style, width, '╤')
.color_option(BrightBlack, color_variant),
);
}
_ => {
println!(
"{}",
Separator::Dashed
.fmt(width, border_style)
.color_option(BrightBlack, color_variant)
);
}
}
println!(
"{} {: <width$}{} {}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
self.temperatures.color_option(Yellow, color_variant).bold(),
temperature_unit.color_option(Yellow, color_variant).bold(),
Border::R.fmt(border_style).color_option(BrightBlack, color_variant),
width = width - 3
);
println!(
"{}",
&Separator::Blank
.fmt(width, border_style)
.color_option(BrightBlack, color_variant)
);
if self.graph.1.chars().count() > 0 {
println!(
"{}{}{}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
self.graph.1.color_option(Yellow, color_variant),
Border::R.fmt(border_style).color_option(BrightBlack, color_variant)
);
}
println!(
"{}{}{}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
self.graph.0.color_option(Yellow, color_variant),
Border::R.fmt(border_style).color_option(BrightBlack, color_variant)
);
println!(
"{} {: <width$}{} {}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant),
self.precipitation.color_option(Blue, color_variant).bold(),
if units.precipitation == Precipitation::probability {
precipitation_unit.color_option(Blue, color_variant).bold()
} else {
precipitation_unit.color_option(Blue, color_variant)
},
Border::R.fmt(border_style).color_option(BrightBlack, color_variant),
width = width - 3
);
match self.time_indicator_col {
Some(_) => {
println!(
"{}",
self.prepare_separator(border_style, width, '╧')
.color_option(BrightBlack, color_variant),
);
}
_ => {
println!(
"{}",
Separator::Dashed
.fmt(width, border_style)
.color_option(BrightBlack, color_variant)
);
}
}
let hours = match units.time {
Time::am_pm => ["¹²·⁰⁰ₐₘ", "³·⁰⁰ₐₘ", "⁶˙⁰⁰ₐₘ", "⁹˙⁰⁰ₐₘ", "¹²˙⁰⁰ₚₘ", "³˙⁰⁰ₚₘ", "⁶˙⁰⁰ₚₘ", "⁹˙⁰⁰ₚₘ"],
_ => ["⁰⁰˙⁰⁰", "⁰³˙⁰⁰", "⁰⁶˙⁰⁰", "⁰⁹˙⁰⁰", "¹²˙⁰⁰", "¹⁵˙⁰⁰", "¹⁸˙⁰⁰", "²¹˙⁰⁰"],
};
print!(
"{}",
Border::L.fmt(border_style).color_option(BrightBlack, color_variant)
);
for hour in hours {
print!("{hour: <9}")
}
println!(
"{}",
Border::R.fmt(border_style).color_option(BrightBlack, color_variant)
);
}
pub fn prepare(
weather: &Weather,
current_hour: usize,
night: bool,
graph_opts: &GraphOpts,
units: &Units,
t: &WeatherCodeLocales,
) -> Result<Self> {
let (temperatures, weather_codes, precipitation);
if current_hour != 23 {
temperatures = &weather.hourly.temperature_2m[..=24];
weather_codes = &weather.hourly.weathercode[..=24];
precipitation = match units.precipitation {
Precipitation::probability => {
Self::prepare_precipitation_probability(&weather.hourly.precipitation_probability[..=24])?
}
_ => Self::prepare_precipitation(&weather.hourly.precipitation[..=24])?,
};
} else {
temperatures = &weather.hourly.temperature_2m[25..=49];
weather_codes = &weather.hourly.weathercode[25..=49];
precipitation = match units.precipitation {
Precipitation::probability => {
Self::prepare_precipitation_probability(&weather.hourly.precipitation_probability[..=24])?
}
_ => Self::prepare_precipitation(&weather.hourly.precipitation[25..=49])?,
};
};
let time_indicator_col = match graph_opts.time_indicator {
true => Some(
if current_hour != 23 { (current_hour * 3) + 3 } else { 1 }
+ (Timelike::minute(&Utc::now()) / 20) as usize,
),
_ => None,
};
let temp_max_min = format!(
"{}/{}{}",
weather.daily.temperature_2m_max[0],
weather.daily.temperature_2m_min[0],
weather.hourly_units.temperature_2m,
);
let precipitation_probability_max = weather.daily.precipitation_probability_max[0];
Ok(HourlyForecast {
temperatures: Self::prepare_temperatures(temperatures, weather_codes, night, t)?,
precipitation,
temp_max_min,
precipitation_probability_max,
graph: Graph::prepare_graph(temperatures, graph_opts)?,
time_indicator_col,
})
}
fn prepare_temperatures(
temperatures: &[f32],
weather_codes: &[u8],
night: bool,
t: &WeatherCodeLocales,
) -> Result<String> {
let mut result = String::new();
for hour in DISPLAY_HOURS {
let temp = temperatures[hour].round() as i32;
let temp_sub = style_number(temp, true)?;
let wmo_code = WeatherCode::resolve(weather_codes[hour], night, t)?;
let colspan = if hour == 0 { 2 } else { 8 };
let _ = write!(result, "{: >colspan$}{}", temp_sub, wmo_code.icon);
}
Ok(result)
}
fn prepare_precipitation(precipitation: &[f32]) -> Result<String> {
let mut result = String::new();
for hour in DISPLAY_HOURS {
let precipitation_sup = style_number(precipitation[hour].ceil() as i32, true)?;
let colspan = if hour == 0 { 2 } else { 8 };
let _ = write!(result, "{precipitation_sup: >colspan$} ");
}
Ok(result)
}
fn prepare_precipitation_probability(precipitation: &[u8]) -> Result<String> {
let mut result = String::new();
for hour in DISPLAY_HOURS {
let precipitation_sup = style_number(precipitation[hour].into(), true)?;
let colspan = if hour == 0 { 2 } else { 8 };
let _ = write!(result, "{precipitation_sup: >colspan$} ");
}
Ok(result)
}
fn prepare_separator(&self, border_variant: &BorderStyle, width: usize, time_indicator_glyph: char) -> String {
let time_indicator_col = self.time_indicator_col.unwrap();
match border_variant {
BorderStyle::double => format!(
"╟{:─>time_indicator_col$}{:─>width$}╢",
time_indicator_glyph,
"",
width = width - time_indicator_col
),
BorderStyle::solid => format!(
"┠{:─>time_indicator_col$}{:─>width$}┨",
time_indicator_glyph,
"",
width = width - time_indicator_col
),
_ => format!(
"├{:┈>time_indicator_col$}{:┈>width$}┤",
time_indicator_glyph,
"",
width = width - time_indicator_col
),
}
}
}