use crate::atom_table::*;
use crate::machine::heap::*;
use crate::types::*;

/* Use the pointer reversal technique of the Deutsch-Schorr-Waite
 * algorithm to detect cycles in Prolog terms.
 *
 * Much of the structure and nomenclature of the GC marking algorithm
 * is adapted here but there are a few significant changes:
 *
 * - Forwarded cells now form a trail of bread crumbs leading back to self.start
 * - Cells are only marked during the backward phase
 * - Visiting subterms of a visited compound does not immediately shift to the backward phase
 * - The heads of LIS structures are both marked and forwarded rather
 *   than just forwarded to distinguish them from tails
 *   continue_forwarding() checks for this before entering the forward
 *   phase
 *
 * Commonalities with the GC marking algorithm:
 * - The contents of forwarded cells are modified only when they are unforwarded
 * - Marked (but unforwarded!) cells immediately shift to the backward phase
*/

#[derive(Debug)]
pub(crate) struct CycleDetectingIter<'a, const STOP_AT_CYCLES: bool> {
    pub(crate) heap: &'a mut Heap,
    start: usize,
    current: usize,
    next: u64,
    cycle_found: bool,
    mark_phase: bool,
}

impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
    pub(crate) fn new(heap: &'a mut Heap, start: usize) -> Self {
        heap[start].set_forwarding_bit(true);
        let next = heap[start].get_value();

        Self {
            heap,
            start,
            current: start,
            next,
            cycle_found: false,
            mark_phase: true,
        }
    }

    #[inline]
    pub(crate) fn cycle_found(&self) -> bool {
        self.cycle_found
    }

    #[inline]
    fn cycle_detection_active(&self) -> bool {
        STOP_AT_CYCLES && self.mark_phase && !self.cycle_found
    }

    fn backward_and_return(&mut self) -> HeapCellValue {
        let mut current = self.heap[self.current];
        current.set_value(self.next);

        if self.backward() {
            // set the f and m bits on the heap cell at start
            // so we invoke backward() and return None next call.

            self.heap[self.current].set_forwarding_bit(false);
            self.heap[self.current].set_mark_bit(self.mark_phase);
        }

        current
    }

    fn traverse_subterm(&mut self, h: usize, arity: usize) -> Option<usize> {
        let mut last_cell_loc = h + arity - 1;

        for idx in (h..h + arity).rev() {
            if self.heap[idx].get_forwarding_bit() {
                if self.cycle_detection_active() {
                    self.cycle_found = true;
                    return None;
                }

                last_cell_loc -= 1;
            } else if self.heap[idx].get_mark_bit() == self.mark_phase {
                last_cell_loc -= 1;
            } else {
                break;
            }
        }

        Some(last_cell_loc)
    }

    #[inline]
    fn continue_forwarding(&self) -> bool {
        self.heap[self.current].get_mark_bit() != self.mark_phase
            || self.heap[self.current].get_forwarding_bit()
    }

    fn forward(&mut self) -> Option<HeapCellValue> {
        loop {
            if self.continue_forwarding() {
                match self.heap[self.current].get_tag() {
                    tag @ HeapCellValueTag::AttrVar | tag @ HeapCellValueTag::Var => {
                        let next = self.next as usize;

                        if self.heap[next].get_forwarding_bit() {
                            return if self.current != next {
                                if self.cycle_detection_active() {
                                    self.cycle_found = true;
                                    None
                                } else {
                                    Some(self.backward_and_return())
                                }
                            } else {
                                Some(self.backward_and_return())
                            };
                        } else if self.heap[next].get_mark_bit() == self.mark_phase {
                            return Some(self.backward_and_return());
                        }

                        self.heap[next].set_forwarding_bit(true);

                        let temp = self.heap[next].get_value();

                        self.heap[next].set_value(self.current as u64);
                        self.current = next;
                        self.next = temp;

                        if self.next < self.heap.cell_len() as u64 {
                            return Some(HeapCellValue::build_with(tag, next as u64));
                        }
                    }
                    HeapCellValueTag::Str => {
                        let h = self.next as usize;
                        let cell = self.heap[h];
                        let arity = cell_as_atom_cell!(self.heap[h]).get_arity();

                        let last_cell_loc = self.traverse_subterm(h + 1, arity)?;

                        if last_cell_loc == h {
                            if self.backward() {
                                return None;
                            }

                            continue;
                        }

                        if self.cycle_detection_active() {
                            for idx in (h + 1..last_cell_loc).rev() {
                                if self.heap[idx].get_forwarding_bit() {
                                    self.cycle_found = true;
                                    return None;
                                }
                            }
                        }

                        self.heap[last_cell_loc].set_forwarding_bit(true);

                        self.next = self.heap[last_cell_loc].get_value();
                        self.heap[last_cell_loc].set_value(self.current as u64);
                        self.current = last_cell_loc;

                        return Some(cell);
                    }
                    HeapCellValueTag::Lis => {
                        let mut cell = self.heap[self.current];
                        cell.set_value(self.next);

                        let last_cell_loc = self.traverse_subterm(self.next as usize, 2)?;

                        if self.cycle_detection_active() {
                            for idx in (self.next as usize..last_cell_loc).rev() {
                                if self.heap[idx].get_forwarding_bit() {
                                    self.cycle_found = true;
                                    return None;
                                }
                            }
                        }

                        if (last_cell_loc + 1) as u64 == self.next {
                            if self.backward() {
                                return None;
                            }

                            continue;
                        } else if last_cell_loc as u64 == self.next {
                            // car cells of lists are both marked and forwarded.
                            self.heap[last_cell_loc].set_mark_bit(self.mark_phase);
                        }

                        self.heap[last_cell_loc].set_forwarding_bit(true);

                        self.next = self.heap[last_cell_loc].get_value();
                        self.heap[last_cell_loc].set_value(self.current as u64);
                        self.current = last_cell_loc;

                        return Some(cell);
                    }
                    HeapCellValueTag::PStrLoc => {
                        let h = self.next as usize;
                        let tail_idx = self.heap.scan_slice_to_str(h).tail_idx;

                        if self.heap[tail_idx].get_forwarding_bit() {
                            if self.cycle_detection_active() {
                                self.cycle_found = true;
                                return None;
                            } else if self.backward() {
                                return None;
                            }

                            continue;
                        }

                        self.heap[tail_idx].set_forwarding_bit(true);

                        self.next = self.heap[tail_idx].get_value();
                        self.heap[tail_idx].set_value(self.current as u64);
                        self.current = tail_idx;

                        return Some(pstr_loc_as_cell!(h));
                    }
                    tag @ HeapCellValueTag::Atom => {
                        let cell = HeapCellValue::build_with(tag, self.next);
                        let arity = AtomCell::from_bytes(cell.into_bytes()).get_arity();

                        if arity == 0 {
                            return Some(self.backward_and_return());
                        } else if self.backward() {
                            return None;
                        }
                    }
                    _ => {
                        return Some(self.backward_and_return());
                    }
                }
            } else if self.backward() {
                return None;
            }
        }
    }

    fn pivot_subterm(&mut self) {
        self.current -= 1;

        let temp = self.heap[self.current + 1].get_value();

        self.heap[self.current + 1].set_value(self.next);
        self.next = self.heap[self.current].get_value();
        self.heap[self.current].set_value(temp);

        self.heap[self.current].set_forwarding_bit(true);
    }

    fn continue_backward(&mut self) -> bool {
        self.heap[self.current].set_forwarding_bit(false);

        if self.current == self.start {
            return false;
        }

        let temp = self.heap[self.current].get_value();

        match self.heap[temp as usize].get_tag() {
            HeapCellValueTag::Str => {
                let mut new_str_back_link = self.current;

                for idx in (0..self.current).rev() {
                    if self.heap[idx].get_tag() == HeapCellValueTag::Atom
                        && cell_as_atom_cell!(self.heap[idx]).get_arity() > 0
                    {
                        new_str_back_link = idx;
                        break;
                    }

                    if self.heap[idx].get_mark_bit() != self.mark_phase
                        && !self.heap[idx].get_forwarding_bit()
                    {
                        new_str_back_link = idx;
                        break;
                    }
                }

                self.heap[self.current].set_mark_bit(self.mark_phase);
                self.heap[self.current].set_value(self.next);

                let back_link_cell = self.heap[new_str_back_link];

                self.next = back_link_cell.get_value();
                self.heap[new_str_back_link].set_value(temp);
                self.current = new_str_back_link;

                read_heap_cell!(back_link_cell,
                    (HeapCellValueTag::Atom, (_name, arity)) => {
                        if arity > 0 {
                            self.heap[self.current].set_mark_bit(self.mark_phase);
                            return true;
                        }
                    }
                    _ => {}
                );

                self.heap[self.current].set_forwarding_bit(true);
                false
            }
            HeapCellValueTag::Lis => {
                if self.heap[self.current].get_mark_bit() == self.mark_phase {
                    true
                } else {
                    self.heap[self.current - 1].set_mark_bit(self.mark_phase);
                    self.heap[self.current].set_mark_bit(self.mark_phase);

                    if self.heap[self.current - 1].get_forwarding_bit() {
                        self.heap[self.current].set_value(self.next);
                        self.next = self.current as u64 - 1;
                        self.current = temp as usize;

                        true
                    } else {
                        self.pivot_subterm();
                        false
                    }
                }
            }
            _ => {
                self.heap[self.current].set_mark_bit(self.mark_phase);
                true
            }
        }
    }

    fn backward(&mut self) -> bool {
        while self.continue_backward() {
            let temp = self.heap[self.current].get_value();

            self.heap[self.current].set_value(self.next);
            self.next = self.current as u64;
            self.current = temp as usize;
        }

        if self.current == self.start {
            return true;
        }

        false
    }

    fn invert_marker(&mut self) {
        self.cycle_found = false;

        if self.heap[self.start].get_forwarding_bit() {
            while !self.backward() {}
        }

        self.mark_phase = false;
        self.heap[self.start].set_forwarding_bit(true);

        self.next = self.heap[self.start].get_value();
        self.current = self.start;

        while self.forward().is_some() {}
    }
}

impl<'a, const STOP_AT_CYCLES: bool> Iterator for CycleDetectingIter<'a, STOP_AT_CYCLES> {
    type Item = HeapCellValue;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.forward()
    }
}

impl<'a, const STOP_AT_CYCLES: bool> Drop for CycleDetectingIter<'a, STOP_AT_CYCLES> {
    fn drop(&mut self) {
        self.invert_marker();

        if self.current == self.start {
            return;
        }

        while !self.backward() {}
    }
}
