use anyhow::Context;
use ron::ser::PrettyConfig;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs::{self, OpenOptions};
use std::io::{Read, Write};
use std::ops::{Index, IndexMut};
use std::path::PathBuf;
use crate::app::account::Account;
use super::File;
use super::account::transactions::Transactions;
use super::crypto::Crypto;
use super::metal::Metal;
use super::money::{Currency, Fiat};
use super::stocks::StockPlus;
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct Accounts {
#[serde(rename = "accounts")]
pub inner: Vec<Account>,
pub groups: Vec<Group>,
pub crypto: Vec<Crypto>,
pub fiats: Vec<Fiat>,
pub metals: Vec<Metal>,
pub stocks_plus: Vec<StockPlus>,
}
impl Accounts {
pub fn currencies(&self) -> HashSet<Fiat> {
let mut currencies = HashSet::new();
for account in &self.inner {
currencies.insert(account.txs_1st.currency.clone());
}
currencies
}
pub fn sort(&mut self) {
self.inner.sort_by_key(|account| account.name.clone());
}
pub fn all_accounts_txs_1st(&self, currency: Fiat) -> Transactions<Fiat> {
let mut transactions = Transactions::new(currency);
for account in &self.inner {
if account.txs_1st.currency == transactions.currency {
for tx in &account.txs_1st.txs {
transactions.txs.push(tx.clone());
}
}
}
transactions.sort();
let mut balance = dec!(0);
for tx in &mut transactions.txs {
balance += tx.amount;
tx.balance = balance;
}
transactions
}
#[must_use]
pub async fn get_all_prices(&mut self) -> Vec<anyhow::Error> {
let mut tasks = Vec::new();
let mut indexes = Vec::new();
for (index, account) in self.inner.iter().enumerate() {
if account.txs_2nd.is_some() {
indexes.push(index);
tasks.push(account.submit_price_as_transaction());
}
}
let results = futures::future::join_all(tasks).await;
let mut errors = Vec::new();
for (index, result) in indexes.into_iter().zip(results) {
let account = &mut self.inner[index];
match result {
Ok(tx) => {
account.txs_1st.txs.push(tx);
account.txs_1st.sort();
}
Err(error) => {
errors.push(error);
}
}
}
errors
}
pub fn get_currencies(&self) -> Vec<Currency> {
let mut currencies = Vec::new();
for crypto in &self.crypto {
currencies.push(Currency::Crypto(crypto.clone()));
}
for fiat in &self.fiats {
currencies.push(Currency::Fiat(fiat.clone()));
}
for metal in &self.metals {
currencies.push(Currency::Metal(metal.clone()));
}
for stock_plus in &self.stocks_plus {
currencies.push(Currency::StockPlus(stock_plus.clone()));
}
currencies
}
pub fn new() -> Self {
Self {
inner: Vec::new(),
groups: Vec::new(),
crypto: Vec::new(),
fiats: Vec::new(),
metals: Vec::new(),
stocks_plus: Vec::new(),
}
}
pub fn balance(&self, currency: &Fiat) -> Decimal {
let mut balance = dec!(0);
for account in &self.inner {
if account.txs_1st.currency == *currency {
balance += account.balance_1st();
}
}
balance
}
pub fn total_for_last_week(&self, currency: &Fiat) -> (Decimal, Decimal) {
let mut previous_total = dec!(0);
let mut total = dec!(0);
for account in &self.inner {
if account.txs_1st.currency == *currency {
let (previous_sum, sum) = account.sum_last_week();
previous_total += previous_sum;
total += sum;
}
}
(previous_total, total)
}
pub fn total_for_last_month(&self, currency: &Fiat) -> (Decimal, Decimal) {
let mut previous_total = dec!(0);
let mut total = dec!(0);
for account in &self.inner {
if account.txs_1st.currency == *currency {
let (previous_sum, sum) = account.sum_last_month();
previous_total += previous_sum;
total += sum;
}
}
(previous_total, total)
}
pub fn total_for_last_year(&self, currency: &Fiat) -> (Decimal, Decimal) {
let mut previous_total = dec!(0);
let mut total = dec!(0);
for account in &self.inner {
if account.txs_1st.currency == *currency {
let (previous_sum, sum) = account.sum_last_year();
previous_total += previous_sum;
total += sum;
}
}
(previous_total, total)
}
pub fn to_string(&self) -> anyhow::Result<String> {
let pretty_config = PrettyConfig::new();
let string = ron::ser::to_string_pretty(self, pretty_config)?;
Ok(string)
}
pub fn save_dialogue(
&self,
old_file: Option<File>,
file_path: PathBuf,
) -> anyhow::Result<File> {
if let Some(old_file) = old_file {
old_file.inner.unlock()?;
}
if fs::exists(&file_path)? {
let file = fs::File::open(&file_path)?;
file.try_lock()?;
file.unlock()?;
}
let mut file = fs::File::create(&file_path)?;
file.try_lock()?;
file.write_all(self.to_string()?.as_bytes())?;
Ok(File {
path: file_path,
inner: file,
})
}
pub fn save_first(&self, file_path: PathBuf) -> anyhow::Result<File> {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&file_path)?;
file.try_lock()?;
file.write_all(self.to_string()?.as_bytes())?;
Ok(File {
path: file_path,
inner: file,
})
}
pub fn save(&self, old_file: Option<File>) -> anyhow::Result<File> {
let old_file = old_file.context("Cannot save because file is None!")?;
let file_path = old_file.path;
let mut file = fs::File::create(&file_path)?;
old_file.inner.unlock()?;
file.try_lock()?;
file.write_all(self.to_string()?.as_bytes())?;
Ok(File {
path: file_path,
inner: file,
})
}
pub fn load(old_file: Option<File>, file_path: PathBuf) -> anyhow::Result<(Self, File)> {
if let Some(old_file) = old_file {
old_file.inner.unlock()?;
}
let mut file = fs::File::open(&file_path)?;
file.try_lock()?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
let accounts = ron::from_str(&buf)?;
Ok((
accounts,
File {
path: file_path,
inner: file,
},
))
}
}
impl Index<usize> for Accounts {
type Output = Account;
fn index(&self, i: usize) -> &Self::Output {
&self.inner[i]
}
}
impl IndexMut<usize> for Accounts {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
&mut self.inner[i]
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Group {
pub name: String,
pub members: Vec<usize>,
}
impl Group {
fn remove_inner(&mut self, index: usize) -> Option<usize> {
for (remove, i) in &mut self.members.iter().enumerate() {
if index == *i {
return Some(remove);
}
}
None
}
pub fn remove(&mut self, index: usize) -> Option<usize> {
for i in &mut self.members.iter_mut() {
if *i > index {
*i -= 1;
}
}
if let Some(index) = self.remove_inner(index) {
return Some(self.members.remove(index));
}
None
}
}