// Selectors operate on IOValues because the AST includes keys of IOValue type.
// If we could make Schemas produce generics...

use crate::CompilationError;
use crate::context::Context;
use crate::predicate::CompiledPredicate;
use crate::predicate::Predicate;
use crate::schemas::path;

use num::bigint::BigInt;
use num::traits::cast::ToPrimitive;
use num::traits::cast::FromPrimitive;

use preserves::value::AtomClass;
use preserves::value::CompoundClass;
use preserves::value::IOValue;
use preserves::value::NestedValue;
use preserves::value::Value;
use preserves::value::ValueClass;

use preserves_schema::support::interpret;

use std::cell::RefCell;
use std::iter::Iterator;
use std::rc::Rc;

pub trait StepMaker {
    fn connect(&self, step: Node) -> Result<Node, CompilationError>;
}

pub trait Step: std::fmt::Debug {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue);
    fn finish(&mut self);
    fn reset(&mut self) -> Vec<IOValue>;
}

macro_rules! delegate_finish_and_reset {
    ($self:ident, $target:expr) => {
        fn finish(&mut $self) { $target.finish() }
        fn reset(&mut $self) -> Vec<IOValue> { $target.reset() }
    }
}

#[derive(Clone, Debug)]
pub struct Node(pub Rc<RefCell<dyn Step>>);

#[derive(Debug)]
struct AxisStep {
    step: Node,
    axis: path::Axis,
}

#[derive(Debug)]
struct CompareStep {
    op: path::Comparison,
    literal: IOValue,
    step: Node,
}

#[derive(Debug)]
struct RegexStep {
    regex: regex::Regex,
    step: Node,
}

#[derive(Debug)]
struct TestStep {
    pred: CompiledPredicate,
    step: Node,
}

#[derive(Debug)]
struct RealStep {
    step: Node,
}

#[derive(Debug)]
struct IntStep {
    step: Node,
}

#[derive(Debug)]
struct VecCollector {
    accumulator: Vec<IOValue>,
}

#[derive(Debug)]
pub struct BoolCollector {
    seen_value: bool,
}

#[derive(Debug)]
struct KindStep {
    kind: ValueClass,
    step: Node,
}

#[derive(Debug)]
pub struct CountCollector {
    count: usize,
}

#[derive(Debug)]
struct CountStep {
    step: Node,
    counter: Node,
}

impl Node {
    fn new<S: Step + 'static>(s: S) -> Self {
        Node(Rc::new(RefCell::new(s)))
    }

    pub fn test(&self, ctxt: &mut Context, value: &IOValue) -> bool {
        !self.exec(ctxt, value).is_empty()
    }

    pub fn accept(&self, ctxt: &mut Context, value: &IOValue) {
        self.0.borrow_mut().accept(ctxt, value)
    }

    pub fn finish(&self) {
        self.0.borrow_mut().finish()
    }

    pub fn reset(&self) -> Vec<IOValue> {
        self.0.borrow_mut().reset()
    }

    pub fn exec(&self, ctxt: &mut Context, value: &IOValue) -> Vec<IOValue> {
        self.accept(ctxt, value);
        self.finish();
        self.reset()
    }
}

impl StepMaker for path::Selector {
    fn connect(&self, step: Node) -> Result<Node, CompilationError> {
        self.0.connect(step)
    }
}

impl<S: StepMaker> StepMaker for Vec<S> {
    fn connect(&self, mut step: Node) -> Result<Node, CompilationError> {
        for s in self.iter().rev() {
            step = s.connect(step)?;
        }
        Ok(step)
    }
}

impl StepMaker for path::Step {
    fn connect(&self, step: Node) -> Result<Node, CompilationError> {
        match self {
            path::Step::Axis(b) => (&**b).connect(step),
            path::Step::Filter(b) => (&**b).connect(step),
            path::Step::Function(b) => (&**b).connect(step),
        }
    }
}

impl StepMaker for path::Axis {
    fn connect(&self, step: Node) -> Result<Node, CompilationError> {
        Ok(Node::new(AxisStep { step, axis: self.clone() }))
    }
}

fn descendants(ctxt: &mut Context, step: &mut Node, v: &IOValue) {
    step.accept(ctxt, v);
    for c in v.value().children() {
        ctxt.with_path_step(&c, |ctxt| descendants(ctxt, step, &c));
    }
}

impl Step for AxisStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        ctxt.with_path_step(value, |ctxt| match &self.axis {
            path::Axis::Values =>
                for c in value.value().children() {
                    self.step.accept(ctxt, &c)
                },
            path::Axis::Descendants =>
                descendants(ctxt, &mut self.step, value),
            path::Axis::At { key } => match value.value() {
                Value::String(s) | Value::Symbol(s) =>
                    step_index(ctxt, s.chars(), &key, |c| IOValue::new(String::from(c)), &mut self.step),
                Value::Record(r) =>
                    step_index(ctxt, r.fields().iter(), &key, |v| v.clone(), &mut self.step),
                Value::Sequence(vs) =>
                    step_index(ctxt, vs.iter(), &key, |v| v.clone(), &mut self.step),
                Value::Dictionary(d) =>
                    if let Some(v) = d.get(&key) {
                        self.step.accept(ctxt, v)
                    },
                _ =>
                    (),
            },
            path::Axis::Label => if let Some(r) = value.value().as_record(None) {
                self.step.accept(ctxt, r.label())
            },
            path::Axis::Keys => match value.value() {
                Value::String(s) | Value::Symbol(s) =>
                    step_keys(ctxt, s.len(), &mut self.step),
                Value::ByteString(bs) => step_keys(ctxt, bs.len(), &mut self.step),
                Value::Record(r) => step_keys(ctxt, r.arity(), &mut self.step),
                Value::Sequence(vs) => step_keys(ctxt, vs.len(), &mut self.step),
                Value::Dictionary(d) =>
                    for k in d.keys() {
                        self.step.accept(ctxt, k)
                    },
                _ => (),
            },
            path::Axis::Length => match value.value() {
                Value::String(s) | Value::Symbol(s) =>
                    self.step.accept(ctxt, &IOValue::new(s.len())),
                Value::ByteString(bs) => self.step.accept(ctxt, &IOValue::new(bs.len())),
                Value::Record(r) => self.step.accept(ctxt, &IOValue::new(r.arity())),
                Value::Sequence(vs) => self.step.accept(ctxt, &IOValue::new(vs.len())),
                Value::Dictionary(d) => self.step.accept(ctxt, &IOValue::new(d.len())),
                _ => self.step.accept(ctxt, &IOValue::new(0)),
            },
            path::Axis::Annotations =>
                for c in value.annotations().slice() {
                    self.step.accept(ctxt, &c)
                },
            path::Axis::Embedded => if let Some(d) = value.value().as_embedded() {
                self.step.accept(ctxt, d)
            },
            path::Axis::Parse { module, name } => {
                if let Some(p) = interpret::Context::new(&ctxt.env.0).dynamic_parse(module, name, value) {
                    self.step.accept(ctxt, &p)
                }
            }
            path::Axis::Unparse { module, name } => {
                if let Some(p) = interpret::Context::new(&ctxt.env.0).dynamic_unparse(module, name, value) {
                    self.step.accept(ctxt, &p)
                }
            }
        })
    }

    delegate_finish_and_reset!(self, self.step);
}

fn step_index<T, Ts: Iterator<Item = T>, F: FnOnce(T) -> IOValue>(
    ctxt: &mut Context,
    mut vs: Ts,
    key: &IOValue,
    f: F,
    step: &mut Node,
) {
    if let Some(i) = key.value().as_usize() {
        match vs.nth(i) {
            None => (),
            Some(v) => step.accept(ctxt, &f(v)),
        }
    }
}

fn step_keys(ctxt: &mut Context, count: usize, step: &mut Node) {
    for i in 0 .. count {
        step.accept(ctxt, &IOValue::new(i))
    }
}

impl StepMaker for path::Filter {
    fn connect(&self, step: Node) -> Result<Node, CompilationError> {
        match self {
            path::Filter::Nop => Ok(step),
            path::Filter::Compare { op, literal } => Ok(Node::new(CompareStep {
                op: (**op).clone(),
                literal: literal.clone(),
                step,
            })),
            path::Filter::Regex { regex } => Ok(Node::new(RegexStep { regex: regex::Regex::new(regex)?, step })),
            path::Filter::Test { pred } => Ok(Node::new(TestStep { pred: (&**pred).compile()?, step })),
            path::Filter::Real => Ok(Node::new(RealStep { step })),
            path::Filter::Int => Ok(Node::new(IntStep { step })),
            path::Filter::Kind { kind } => Ok(Node::new(KindStep {
                kind: match &**kind {
                    path::ValueKind::Boolean => ValueClass::Atomic(AtomClass::Boolean),
                    path::ValueKind::Float => ValueClass::Atomic(AtomClass::Float),
                    path::ValueKind::Double => ValueClass::Atomic(AtomClass::Double),
                    path::ValueKind::SignedInteger => ValueClass::Atomic(AtomClass::SignedInteger),
                    path::ValueKind::String => ValueClass::Atomic(AtomClass::String),
                    path::ValueKind::ByteString => ValueClass::Atomic(AtomClass::ByteString),
                    path::ValueKind::Symbol => ValueClass::Atomic(AtomClass::Symbol),
                    path::ValueKind::Record => ValueClass::Compound(CompoundClass::Record),
                    path::ValueKind::Sequence => ValueClass::Compound(CompoundClass::Sequence),
                    path::ValueKind::Set => ValueClass::Compound(CompoundClass::Set),
                    path::ValueKind::Dictionary => ValueClass::Compound(CompoundClass::Dictionary),
                    path::ValueKind::Embedded => ValueClass::Embedded,
                },
                step,
            })),
        }
    }
}

impl Step for CompareStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        if match self.op {
            path::Comparison::Eq => value == &self.literal,
            path::Comparison::Ne => value != &self.literal,
            path::Comparison::Lt => value < &self.literal,
            path::Comparison::Ge => value >= &self.literal,
            path::Comparison::Gt => value > &self.literal,
            path::Comparison::Le => value <= &self.literal,
        } {
            self.step.accept(ctxt, value)
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl Step for RegexStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        match value.value() {
            Value::String(s) | Value::Symbol(s) =>
                if self.regex.is_match(s) { self.step.accept(ctxt, value) },
            _ =>
                (),
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl Step for TestStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        if self.pred.test(ctxt, value) {
            self.step.accept(ctxt, value)
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl Step for RealStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        match value.value() {
            Value::SignedInteger(i) => if let Some(r) = BigInt::from(i).to_f64() {
                self.step.accept(ctxt, &IOValue::new(r))
            },
            Value::Float(f) => self.step.accept(ctxt, &IOValue::new(f32::from(*f) as f64)),
            Value::Double(_) => self.step.accept(ctxt, value),
            _ => (),
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl Step for IntStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        match value.value() {
            Value::SignedInteger(_) => self.step.accept(ctxt, value),
            Value::Float(f) => if let Some(i) = BigInt::from_f32(f32::from(*f)) {
                self.step.accept(ctxt, &IOValue::new(i))
            },
            Value::Double(d) => if let Some(i) = BigInt::from_f64(f64::from(*d)) {
                self.step.accept(ctxt, &IOValue::new(i))
            },
            _ => (),
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl VecCollector {
    fn new() -> Node {
        Node::new(VecCollector { accumulator: Vec::new() })
    }
}

impl Step for VecCollector {
    fn accept(&mut self, _ctxt: &mut Context, value: &IOValue) {
        self.accumulator.push(value.clone())
    }

    fn finish(&mut self) {
    }

    fn reset(&mut self) -> Vec<IOValue> {
        std::mem::take(&mut self.accumulator)
    }
}

impl BoolCollector {
    pub fn new() -> Node {
        Node::new(BoolCollector { seen_value: false })
    }
}

impl Step for BoolCollector {
    fn accept(&mut self, _ctxt: &mut Context, _value: &IOValue) {
        self.seen_value = true
    }

    fn finish(&mut self) {
    }

    fn reset(&mut self) -> Vec<IOValue> {
        let result = if self.seen_value { vec![IOValue::new(true)] } else { vec![] };
        self.seen_value = false;
        result
    }
}

impl Step for KindStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        if value.value_class() == self.kind {
            self.step.accept(ctxt, value)
        }
    }

    delegate_finish_and_reset!(self, self.step);
}

impl path::Selector {
    pub fn compile(&self) -> Result<Node, CompilationError> {
        self.connect(VecCollector::new())
    }

    pub fn exec(&self, ctxt: &mut Context, value: &IOValue) -> Result<Vec<IOValue>, CompilationError> {
        Ok(self.compile()?.exec(ctxt, value))
    }
}

impl StepMaker for path::Function {
    fn connect(&self, step: Node) -> Result<Node, CompilationError> {
        // For now, there's just one function: `count`.
        Ok(Node::new(CountStep { step, counter: self.selector.connect(CountCollector::new())? }))
    }
}

impl CountCollector {
    pub fn new() -> Node {
        Node::new(CountCollector { count: 0 })
    }
}

impl Step for CountCollector {
    fn accept(&mut self, _ctxt: &mut Context, _value: &IOValue) {
        self.count += 1
    }

    fn finish(&mut self) {
    }

    fn reset(&mut self) -> Vec<IOValue> {
        let result = vec![IOValue::new(self.count)];
        self.count = 0;
        result
    }
}

impl Step for CountStep {
    fn accept(&mut self, ctxt: &mut Context, value: &IOValue) {
        let count = &self.counter.exec(ctxt, value)[0];
        self.step.accept(ctxt, count)
    }

    delegate_finish_and_reset!(self, self.step);
}
