use crate::pristine::*;
use crate::vertex_buffer;
use std::collections::{HashMap, HashSet};
pub(super) struct Diff {
    pub buf: Vec<u8>,
    pub inode: Position<Option<ChangeId>>,
    pub path: String,
    pub contents_a: Vec<u8>,
    pub pos_a: Vec<Vertex>,
    pub missing_eol: HashSet<usize>,
    pub marker: HashMap<usize, ConflictMarker>,
    conflict_stack: Vec<Conflict>,
    pub conflict_ends: Vec<ConflictEnds>,
    pub solved_conflicts: HashSet<usize>,
    pub cyclic_conflict_bytes: Vec<(usize, usize)>,
}

#[derive(Debug, Clone)]
pub struct Conflict {
    pub counter: usize,
    pub side: usize,
    pub conflict_type: ConflictType,
}

#[derive(Debug, Clone, Copy)]
pub enum ConflictType {
    Root,
    Order,
    Zombie,
    Cyclic,
}

#[derive(Debug)]
pub struct ConflictEnds {
    pub start: usize,
    pub end: usize,
    pub end_pos: usize,
    pub conflict_type: ConflictType,
}

#[derive(Debug, PartialEq, Eq)]
pub enum ConflictMarker {
    Begin,
    Next,
    End,
}

#[derive(Debug)]
pub struct Vertex {
    pub pos: usize,
    pub vertex: crate::pristine::Vertex<ChangeId>,
    pub before_conflict: bool,
    pub conflict: usize,
}

impl Diff {
    pub fn new(
        inode: Position<Option<ChangeId>>,
        path: String,
        graph: &crate::alive::Graph,
    ) -> Self {
        Diff {
            inode,
            path,
            buf: Vec::with_capacity(graph.len_bytes()),
            pos_a: Vec::with_capacity(2 * graph.len_vertices()),
            contents_a: Vec::with_capacity(graph.len_bytes()),
            missing_eol: HashSet::new(),
            conflict_ends: vec![ConflictEnds {
                start: 0,
                end: 0,
                end_pos: 0,
                conflict_type: ConflictType::Root,
            }],
            marker: HashMap::new(),
            conflict_stack: vec![Conflict {
                counter: 0,
                side: 0,
                conflict_type: ConflictType::Root,
            }],
            cyclic_conflict_bytes: Vec::new(),
            solved_conflicts: HashSet::new(),
        }
    }
}

impl Diff {
    pub fn vertex(
        &self,
        i: usize,
        pos: usize,
        end_pos: usize,
    ) -> crate::pristine::Vertex<ChangeId> {
        let mut v = self.pos_a[i].vertex;
        assert!(!v.is_root());
        if pos > self.pos_a[i].pos {
            v.start =
                ChangePosition(self.pos_a[i].vertex.start.0 + (pos - self.pos_a[i].pos) as u64)
        }
        if i + 1 >= self.pos_a.len() || end_pos < self.pos_a[i + 1].pos {
            v.end =
                ChangePosition(self.pos_a[i].vertex.start.0 + (end_pos - self.pos_a[i].pos) as u64)
        }
        v
    }
    pub fn position(&self, i: usize, pos: usize) -> crate::pristine::Position<ChangeId> {
        let mut v = self.pos_a[i].vertex.start_pos();
        if pos > self.pos_a[i].pos {
            v.pos = ChangePosition(self.pos_a[i].vertex.start.0 + (pos - self.pos_a[i].pos) as u64)
        }
        v
    }
}

impl Diff {
    fn begin_conflict_(&mut self, conflict_type: ConflictType) {
        self.conflict_stack.push(Conflict {
            counter: self.conflict_ends.len(),
            side: 0,
            conflict_type,
        });
        let len = match self.contents_a.last() {
            Some(&b'\n') | None => self.contents_a.len(),
            _ => {
                self.missing_eol.insert(self.contents_a.len());
                self.contents_a.len() + 1
            }
        };
        self.conflict_ends.push(ConflictEnds {
            start: self.pos_a.len(),
            end: self.pos_a.len(),
            end_pos: len,
            conflict_type,
        });
        self.marker.insert(len, ConflictMarker::Begin);
    }
}

impl vertex_buffer::VertexBuffer for Diff {
    fn output_line<E, C>(&mut self, v: crate::pristine::Vertex<ChangeId>, c: C) -> Result<(), E>
    where
        E: From<std::io::Error>,
        C: FnOnce(&mut Vec<u8>) -> Result<(), E>,
    {
        if v == crate::pristine::Vertex::BOTTOM {
            return Ok(());
        }
        self.buf.clear();
        c(&mut self.buf)?;
        self.pos_a.push(Vertex {
            pos: self.contents_a.len(),
            vertex: v,
            before_conflict: false,
            conflict: self.conflict_stack.last().unwrap().counter,
        });
        self.contents_a.extend(&self.buf);
        Ok(())
    }

    fn begin_conflict(&mut self) -> Result<(), std::io::Error> {
        self.begin_conflict_(ConflictType::Order);
        self.output_conflict_marker(vertex_buffer::START_MARKER)
    }

    fn begin_cyclic_conflict(&mut self) -> Result<(), std::io::Error> {
        let len = self.contents_a.len();
        self.begin_conflict_(ConflictType::Cyclic);
        self.cyclic_conflict_bytes.push((len, len));
        self.output_conflict_marker(vertex_buffer::START_MARKER)
    }

    fn begin_zombie_conflict(&mut self) -> Result<(), std::io::Error> {
        self.begin_conflict_(ConflictType::Zombie);
        self.output_conflict_marker(vertex_buffer::START_MARKER)
    }

    fn end_conflict(&mut self) -> Result<(), std::io::Error> {
        let len = match self.contents_a.last() {
            Some(&b'\n') | None => self.contents_a.len(),
            _ => {
                self.missing_eol.insert(self.contents_a.len());
                self.contents_a.len() + 1
            }
        };
        let chunk = self.pos_a.len();
        self.output_conflict_marker(vertex_buffer::END_MARKER)?;
        let conflict = self.conflict_stack.pop().unwrap();
        self.marker.insert(len, ConflictMarker::End);
        self.conflict_ends[conflict.counter].end_pos = len;
        self.conflict_ends[conflict.counter].end = chunk;
        Ok(())
    }
    fn end_cyclic_conflict(&mut self) -> Result<(), std::io::Error> {
        debug!("end_cyclic_conflict");
        self.end_conflict()?;
        self.cyclic_conflict_bytes.last_mut().unwrap().1 = self.contents_a.len();
        Ok(())
    }

    fn conflict_next(&mut self) -> Result<(), std::io::Error> {
        let len = match self.contents_a.last() {
            Some(&b'\n') | None => self.contents_a.len(),
            _ => {
                self.missing_eol.insert(self.contents_a.len());
                self.contents_a.len() + 1
            }
        };
        self.conflict_stack.last_mut().unwrap().side += 1;
        self.marker.insert(len, ConflictMarker::Next);
        self.output_conflict_marker(vertex_buffer::SEPARATOR)
    }

    fn output_conflict_marker(&mut self, marker: &str) -> Result<(), std::io::Error> {
        if let Some(line) = self.pos_a.last_mut() {
            line.before_conflict = true
        }
        debug!(
            "output_conflict_marker {:?} {:?}",
            self.contents_a.last(),
            marker
        );
        let pos = match self.contents_a.last() {
            Some(&b'\n') | None => {
                let len = self.contents_a.len();
                self.contents_a.extend(marker.as_bytes().iter().skip(1));
                len
            }
            _ => {
                let len = self.contents_a.len() + 1;
                self.contents_a.extend(marker.as_bytes().iter());
                len
            }
        };
        self.pos_a.push(Vertex {
            pos,
            vertex: crate::pristine::Vertex::ROOT,
            before_conflict: false,
            conflict: self.conflict_stack.last().unwrap().counter,
        });
        Ok(())
    }
}

impl Diff {
    pub fn last_vertex_containing(&self, pos: usize) -> usize {
        match self.pos_a.binary_search_by(|l| l.pos.cmp(&pos)) {
            Ok(mut i) => loop {
                if i + 1 >= self.pos_a.len() {
                    return i;
                }
                if self.pos_a[i].pos == self.pos_a[i + 1].pos {
                    i += 1
                } else {
                    return i;
                }
            },
            Err(i) => {
                assert!(i > 0);
                i - 1
            }
        }
    }
    pub fn first_vertex_containing(&self, pos: usize) -> usize {
        match self.pos_a.binary_search_by(|l| l.pos.cmp(&pos)) {
            Ok(mut i) => loop {
                if i == 0 {
                    return 0;
                }
                if self.pos_a[i].pos == self.pos_a[i - 1].pos {
                    i -= 1
                } else {
                    return i;
                }
            },
            Err(i) => {
                assert!(i > 0);
                i - 1
            }
        }
    }
}