use std::convert::TryFrom;
use std::fmt;
use std::str;
use serde::{Serialize, Deserialize};
use crate::{error::EpbdError, types::RenNrenCo2};
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Carrier {
ELECTRICIDAD,
MEDIOAMBIENTE,
BIOCARBURANTE,
BIOMASA,
BIOMASADENSIFICADA,
CARBON,
GASNATURAL,
GASOLEO,
GLP,
RED1,
RED2,
}
impl str::FromStr for Carrier {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Carrier, Self::Err> {
match s {
"ELECTRICIDAD" => Ok(Carrier::ELECTRICIDAD),
"MEDIOAMBIENTE" => Ok(Carrier::MEDIOAMBIENTE),
"BIOCARBURANTE" => Ok(Carrier::BIOCARBURANTE),
"BIOMASA" => Ok(Carrier::BIOMASA),
"BIOMASADENSIFICADA" => Ok(Carrier::BIOMASADENSIFICADA),
"CARBON" => Ok(Carrier::CARBON),
"GASNATURAL" => Ok(Carrier::GASNATURAL),
"GASOLEO" => Ok(Carrier::GASOLEO),
"GLP" => Ok(Carrier::GLP),
"RED1" => Ok(Carrier::RED1),
"RED2" => Ok(Carrier::RED2),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for Carrier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum CType {
PRODUCCION,
CONSUMO,
}
impl str::FromStr for CType {
type Err = EpbdError;
fn from_str(s: &str) -> Result<CType, Self::Err> {
match s {
"PRODUCCION" => Ok(CType::PRODUCCION),
"CONSUMO" => Ok(CType::CONSUMO),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for CType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum CSubtype {
INSITU,
COGENERACION,
EPB,
NEPB,
}
impl str::FromStr for CSubtype {
type Err = EpbdError;
fn from_str(s: &str) -> Result<CSubtype, Self::Err> {
match s {
"INSITU" => Ok(CSubtype::INSITU),
"COGENERACION" => Ok(CSubtype::COGENERACION),
"EPB" => Ok(CSubtype::EPB),
"NEPB" => Ok(CSubtype::NEPB),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for CSubtype {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Service {
ACS,
CAL,
REF,
VEN,
ILU,
HU,
DHU,
BAC,
NDEF,
}
pub const SERVICES: [Service; 9] = [
Service::ACS,
Service::CAL,
Service::REF,
Service::VEN,
Service::ILU,
Service::HU,
Service::DHU,
Service::BAC,
Service::NDEF,
];
impl str::FromStr for Service {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Service, Self::Err> {
match s {
"ACS" => Ok(Service::ACS),
"WATERSYSTEMS" => Ok(Service::ACS),
"CAL" => Ok(Service::CAL),
"HEATING" => Ok(Service::CAL),
"REF" => Ok(Service::REF),
"COOLING" => Ok(Service::REF),
"VEN" => Ok(Service::VEN),
"FANS" => Ok(Service::VEN),
"ILU" => Ok(Service::ILU),
"HU" => Ok(Service::HU),
"DHU" => Ok(Service::DHU),
"BAC" => Ok(Service::BAC),
"NDEF" => Ok(Service::NDEF),
"" => Ok(Service::default()),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for Service {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Default for Service {
fn default() -> Service {
Service::NDEF
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Component {
pub carrier: Carrier,
pub ctype: CType,
pub csubtype: CSubtype,
pub service: Service,
pub values: Vec<f32>,
pub comment: String,
}
impl fmt::Display for Component {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let valuelist = self
.values
.iter()
.map(|v| format!("{:.2}", v))
.collect::<Vec<_>>()
.join(", ");
let comment = if self.comment != "" {
format!(" # {}", self.comment)
} else {
"".to_owned()
};
write!(
f,
"{}, {}, {}, {}, {}{}",
self.carrier, self.ctype, self.csubtype, self.service, valuelist, comment
)
}
}
impl str::FromStr for Component {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Component, Self::Err> {
use self::CSubtype::*;
use self::CType::*;
use self::Carrier::{ELECTRICIDAD, MEDIOAMBIENTE};
let items: Vec<&str> = s.trim().splitn(2, '#').map(str::trim).collect();
let comment = items.get(1).unwrap_or(&"").to_string();
let items: Vec<&str> = items[0].split(',').map(str::trim).collect();
if items.len() < 4 {
return Err(EpbdError::ParseError(s.into()));
};
let carrier: Carrier = items[0]
.parse()
.map_err(|_| EpbdError::ParseError(items[0].into()))?;
let ctype: CType = items[1]
.parse()
.map_err(|_| EpbdError::ParseError(items[1].into()))?;
let csubtype: CSubtype = items[2]
.parse()
.map_err(|_| EpbdError::ParseError(items[2].into()))?;
let carrier_ok = match ctype {
CONSUMO => match csubtype {
EPB | NEPB => true,
_ => false,
},
PRODUCCION => match csubtype {
INSITU => carrier == ELECTRICIDAD || carrier == MEDIOAMBIENTE,
COGENERACION => carrier == ELECTRICIDAD,
_ => false,
},
};
if !carrier_ok {
return Err(EpbdError::ParseError(s.into()));
}
let maybeservice: Result<Service, _> = items[3].parse();
let (valuesidx, service) = match maybeservice {
Ok(s) => (4, s),
Err(_) => (3, Service::default()),
};
let values = items[valuesidx..]
.iter()
.map(|v| v.parse::<f32>())
.collect::<Result<Vec<f32>, _>>()?;
Ok(Component {
carrier,
ctype,
csubtype,
service,
values,
comment,
})
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum Source {
RED,
INSITU,
COGENERACION,
}
impl str::FromStr for Source {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Source, Self::Err> {
match s {
"RED" => Ok(Source::RED),
"INSITU" => Ok(Source::INSITU),
"COGENERACION" => Ok(Source::COGENERACION),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl TryFrom<CSubtype> for Source {
type Error = EpbdError;
fn try_from(subtype: CSubtype) -> Result<Self, Self::Error> {
match subtype {
CSubtype::INSITU => Ok(Self::INSITU),
CSubtype::COGENERACION => Ok(Self::COGENERACION),
_ => Err(EpbdError::ParseError(format!("CSubtype as Source {}", subtype))),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum Dest {
SUMINISTRO,
A_RED,
A_NEPB,
}
impl str::FromStr for Dest {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Dest, Self::Err> {
match s {
"SUMINISTRO" => Ok(Dest::SUMINISTRO),
"A_RED" => Ok(Dest::A_RED),
"A_NEPB" => Ok(Dest::A_NEPB),
"to_grid" => Ok(Dest::A_RED),
"to_nEPB" => Ok(Dest::A_NEPB),
"input" => Ok(Dest::SUMINISTRO),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for Dest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum Step {
A,
B,
}
impl str::FromStr for Step {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Step, Self::Err> {
match s {
"A" => Ok(Step::A),
"B" => Ok(Step::B),
_ => Err(EpbdError::ParseError(s.into())),
}
}
}
impl std::fmt::Display for Step {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Factor {
pub carrier: Carrier,
pub source: Source,
pub dest: Dest,
pub step: Step,
pub ren: f32,
pub nren: f32,
pub co2: f32,
pub comment: String,
}
impl Factor {
pub fn new<T: Into<String>>(
carrier: Carrier,
source: Source,
dest: Dest,
step: Step,
RenNrenCo2 { ren, nren, co2 }: RenNrenCo2,
comment: T,
) -> Self {
Self {
carrier,
source,
dest,
step,
ren,
nren,
co2,
comment: comment.into(),
}
}
pub fn factors(&self) -> RenNrenCo2 {
RenNrenCo2 {
ren: self.ren,
nren: self.nren,
co2: self.co2,
}
}
pub fn set_values(&mut self, &values: &RenNrenCo2) {
self.ren = values.ren;
self.nren = values.nren;
self.co2 = values.co2;
}
}
impl fmt::Display for Factor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let comment = if self.comment != "" {
format!(" # {}", self.comment)
} else {
"".to_owned()
};
write!(
f,
"{}, {}, {}, {}, {:.3}, {:.3}, {:.3}{}",
self.carrier, self.source, self.dest, self.step, self.ren, self.nren, self.co2, comment
)
}
}
impl str::FromStr for Factor {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Factor, Self::Err> {
let items: Vec<&str> = s.trim().splitn(2, '#').map(str::trim).collect();
let comment = items.get(1).unwrap_or(&"").to_string();
let items: Vec<&str> = items[0].split(',').map(str::trim).collect();
if items.len() < 7 {
return Err(EpbdError::ParseError(s.into()));
};
let carrier: Carrier = items[0]
.parse()
.map_err(|_| EpbdError::ParseError(items[0].into()))?;
let source: Source = items[1]
.parse()
.map_err(|_| EpbdError::ParseError(items[1].into()))?;
let dest: Dest = items[2]
.parse()
.map_err(|_| EpbdError::ParseError(items[2].into()))?;
let step: Step = items[3]
.parse()
.map_err(|_| EpbdError::ParseError(items[3].into()))?;
let ren: f32 = items[4].parse()?;
let nren: f32 = items[5].parse()?;
let co2: f32 = items[6].parse()?;
Ok(Factor {
carrier,
source,
dest,
step,
ren,
nren,
co2,
comment,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn tcomponent() {
let component1 = Component {
carrier: "ELECTRICIDAD".parse().unwrap(),
ctype: "CONSUMO".parse().unwrap(),
csubtype: "EPB".parse().unwrap(),
service: "REF".parse().unwrap(),
values: vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
],
comment: "Comentario cons 1".into(),
};
let component1str = "ELECTRICIDAD, CONSUMO, EPB, REF, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00, 11.00, 12.00 # Comentario cons 1";
let component2 = Component {
carrier: "ELECTRICIDAD".parse().unwrap(),
ctype: "PRODUCCION".parse().unwrap(),
csubtype: "INSITU".parse().unwrap(),
service: "NDEF".parse().unwrap(),
values: vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
],
comment: "Comentario prod 1".into(),
};
let component2str = "ELECTRICIDAD, PRODUCCION, INSITU, NDEF, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00, 11.00, 12.00 # Comentario prod 1";
let component2strlegacy = "ELECTRICIDAD, PRODUCCION, INSITU, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00, 11.00, 12.00 # Comentario prod 1";
assert_eq!(component1.to_string(), component1str);
assert_eq!(component2.to_string(), component2str);
assert_eq!(
component2str.parse::<Component>().unwrap().to_string(),
component2str
);
assert_eq!(
component2strlegacy
.parse::<Component>()
.unwrap()
.to_string(),
component2str
);
}
#[test]
fn tfactor() {
let factor1 = Factor {
carrier: "ELECTRICIDAD".parse().unwrap(),
source: "RED".parse().unwrap(),
dest: "SUMINISTRO".parse().unwrap(),
step: "A".parse().unwrap(),
ren: 0.414,
nren: 1.954,
co2: 0.331,
comment: "Electricidad de red paso A".into(),
};
let factor1str =
"ELECTRICIDAD, RED, SUMINISTRO, A, 0.414, 1.954, 0.331 # Electricidad de red paso A";
let factor2str = "ELECTRICIDAD, PRODUCCION, INSITU, NDEF, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00, 11.00, 12.00 # Comentario prod 1";
assert_eq!(factor1.to_string(), factor1str);
assert_eq!(
factor2str.parse::<Component>().unwrap().to_string(),
factor2str
);
}
}