logisheets_controller 0.6.0

the core of LogiSheets
Documentation
use super::condition::{
    get_condition_value, match_condition, parse_condition, Condition, LogicalCondition, Op,
};
use crate::calc_engine::connector::Connector;
use itertools::Itertools;
use logisheets_parser::ast;

use super::{CalcValue, CalcVertex, Value};

pub fn calc_sumif<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 { a + b };
    let result = |sum: f64, _: f64| CalcVertex::from_number(sum);
    calc_if(args, fetcher, &result, &sum_func)
}

pub fn calc_averageif<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 { a + b };
    let result = |sum: f64, cnt: f64| {
        if cnt == 0. {
            CalcVertex::from_error(ast::Error::Div0)
        } else {
            CalcVertex::from_number(sum / cnt)
        }
    };
    calc_if(args, fetcher, &result, &sum_func)
}

pub fn calc_sumifs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 { a + b };
    let result = |sum: f64, _: f64| CalcVertex::from_number(sum);
    calc_ifs(args, fetcher, &result, &sum_func)
}

pub fn calc_averageifs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 { a + b };
    let result = |sum: f64, cnt: f64| {
        if cnt == 0. {
            CalcVertex::from_error(ast::Error::Div0)
        } else {
            CalcVertex::from_number(sum / cnt)
        }
    };
    calc_ifs(args, fetcher, &result, &sum_func)
}

pub fn calc_maxifs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 {
        if a > b {
            a
        } else {
            b
        }
    };
    let result = |sum: f64, _: f64| CalcVertex::from_number(sum);
    calc_ifs(args, fetcher, &result, &sum_func)
}

pub fn calc_minifs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let sum_func = |a: f64, b: f64| -> f64 {
        if a > b {
            b
        } else {
            a
        }
    };
    let result = |sum: f64, _: f64| CalcVertex::from_number(sum);
    calc_ifs(args, fetcher, &result, &sum_func)
}

pub fn calc_countifs<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    let func = |_, _| -> f64 { 0. };
    let result = |_: f64, cnt: f64| CalcVertex::from_number(cnt);
    calc_ifs(args, fetcher, &result, &func)
}

fn calc_ifs<C, F, SF>(args: Vec<CalcVertex>, fetcher: &mut C, f: &F, sum_func: &SF) -> CalcVertex
where
    C: Connector,
    F: Fn(f64, f64) -> CalcVertex,
    SF: Fn(f64, f64) -> f64,
{
    assert_or_return!(args.len() % 2 == 1, ast::Error::Unspecified);
    let pair_len = (args.len() - 1) / 2;
    let mut args_iter = args.into_iter();
    let first = fetcher.get_calc_value(args_iter.next().unwrap());
    assert_range_from_calc_value!(calc_range, first);
    let mut pair: Vec<Criteria<_>> = Vec::with_capacity(pair_len);

    let mut pair_iter = args_iter.peekable();
    while pair_iter.peek().is_some() {
        let c = fetcher.get_calc_value(pair_iter.next().unwrap());
        let cr = fetcher.get_calc_value(pair_iter.next().unwrap());
        assert_range_from_calc_value!(range, c);
        let condition = match &cr {
            CalcValue::Scalar(v) => match v {
                Value::Text(t) => parse_condition(t),
                _ => {
                    let value = get_condition_value(&v);
                    let condition = Condition::Logical(LogicalCondition { op: Op::Eq, value });
                    Some(condition)
                }
            },
            _ => None,
        };
        assert_or_return!(condition.is_some(), ast::Error::Value);
        let condition = condition.unwrap();
        let criteria = Criteria {
            data: range
                .into_iter()
                .map(|v| match_condition(&condition, &v))
                .collect_vec()
                .into_iter(),
        };
        pair.push(criteria);
    }

    let mut sum = 0.;
    let mut cnt = 0.;
    let mut calc_range = calc_range.into_iter();
    let mut curr_value = calc_range.next();
    while curr_value.is_some() {
        let mut all_true = true;
        for i in pair.iter_mut() {
            if let Some(b) = i.next() {
                if !b {
                    all_true = b;
                    break;
                }
            } else {
                all_true = false;
                break;
            }
        }
        if all_true {
            let v = curr_value.unwrap();
            sum = sum_func(sum, get_num_from_value(v));
            cnt += 1.;
        }
        curr_value = calc_range.next();
    }
    f(sum, cnt)
}

struct Criteria<T: Iterator<Item = bool>> {
    pub data: T,
}

impl<T: Iterator<Item = bool>> Criteria<T> {
    pub fn next(&mut self) -> Option<bool> {
        let v = self.data.next()?;
        Some(v)
    }
}

fn calc_if<C, F, SF>(args: Vec<CalcVertex>, fetcher: &mut C, f: &F, sum_func: &SF) -> CalcVertex
where
    C: Connector,
    F: Fn(f64, f64) -> CalcVertex,
    SF: Fn(f64, f64) -> f64,
{
    assert_or_return!(args.len() >= 2 && args.len() <= 3, ast::Error::Unspecified);
    let mut args_iter = args.into_iter();
    let first = fetcher.get_calc_value(args_iter.next().unwrap());
    assert_range_from_calc_value!(range, first);
    let second = fetcher.get_calc_value(args_iter.next().unwrap());
    let condition = match &second {
        CalcValue::Scalar(v) => match v {
            Value::Text(t) => parse_condition(t),
            _ => {
                let value = get_condition_value(&v);
                let condition = Condition::Logical(LogicalCondition { op: Op::Eq, value });
                Some(condition)
            }
        },
        _ => None,
    };
    if condition.is_none() {
        return CalcVertex::from_error(ast::Error::Value);
    }

    let condition = condition.unwrap();

    let mut sum = 0.;
    let mut cnt = 0.;

    if let Some(third) = args_iter.next() {
        let sum_range = fetcher.get_calc_value(third);
        assert_range_from_calc_value!(sum_range, sum_range);
        sum_range
            .into_iter()
            .zip(range.into_iter())
            .for_each(|(c, s)| {
                if match_condition(&condition, &s) {
                    sum = sum_func(sum, get_num_from_value(c));
                    cnt += 1.;
                }
            })
    } else {
        range.into_iter().for_each(|v| {
            if match_condition(&condition, &v) {
                sum = sum_func(sum, get_num_from_value(v));
                cnt += 1.;
            }
        })
    };
    f(sum, cnt)
}

#[inline]
fn get_num_from_value(v: Value) -> f64 {
    match v {
        Value::Number(n) => n,
        _ => 0.,
    }
}