omap 0.6.1

Interact with or write new Open Orienteering Mapper omap-files
Documentation
use quick_xml::{
    Reader, Writer,
    events::{BytesEnd, BytesStart, Event},
};

use super::MapPart;
use crate::{Error, Result, symbols::SymbolSet};

/// An ordered collection of map parts (layers).
#[derive(Debug, Default, Clone)]
pub struct MapParts(pub Vec<MapPart>);

impl MapParts {
    /// Add a new map part to the end of the collection.
    pub fn push(&mut self, part: MapPart) {
        self.0.push(part);
    }

    /// Get the number of map parts.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Returns `true` if there are no map parts.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Merge all map parts into a single part
    /// If new_name is some, then the new name is applied, else the name of the first map part is kept
    pub fn merge_all_parts(&mut self, new_name: Option<String>) {
        while let Some(part) = self.0.pop() {
            if let Some(first) = self.0.first_mut() {
                first.merge(part);
            } else {
                self.0.push(part);
                break;
            }
        }

        if let Some(name) = new_name
            && let Some(first) = self.0.first_mut()
        {
            first.name = name;
        }
    }

    /// Merge two of the map parts.
    /// The second part is merged into the first part. The name of the first part is kept
    /// The order of parts is also kept
    pub fn merge_two_parts(&mut self, mut part_1_index: usize, part_2_index: usize) -> Result<()> {
        if part_1_index >= self.len() || part_2_index >= self.len() || part_1_index == part_2_index
        {
            Err(Error::MapPartMergeError)
        } else {
            let part2 = self.0.remove(part_2_index);
            if part_1_index > part_2_index {
                part_1_index -= 1;
            }
            self.0[part_1_index].merge(part2);
            Ok(())
        }
    }

    /// Remove and return the map part at the given index, or `None` if out of bounds.
    pub fn remove_map_part_by_index(&mut self, index: usize) -> Option<MapPart> {
        if index < self.len() {
            Some(self.0.remove(index))
        } else {
            None
        }
    }

    /// Case sensitive
    pub fn get_map_part_by_name(&self, name: &str) -> Option<&MapPart> {
        self.0.iter().find(|p| p.name.as_str() == name)
    }

    /// Case sensitive
    pub fn get_map_part_by_name_mut(&mut self, name: &str) -> Option<&mut MapPart> {
        self.0.iter_mut().find(|p| p.name.as_str() == name)
    }

    /// Get a map part by its index.
    pub fn get_map_part_by_index(&self, index: usize) -> Option<&MapPart> {
        if index >= self.0.len() {
            None
        } else {
            Some(&self.0[index])
        }
    }

    /// Get a mutable reference to a map part by its index.
    pub fn get_map_part_by_index_mut(&mut self, index: usize) -> Option<&mut MapPart> {
        if index >= self.0.len() {
            None
        } else {
            Some(&mut self.0[index])
        }
    }

    /// Iterate over map parts.
    pub fn iter(&self) -> std::slice::Iter<'_, MapPart> {
        self.0.iter()
    }

    /// Iterate mutably over map parts.
    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, MapPart> {
        self.0.iter_mut()
    }
}

impl MapParts {
    pub(crate) fn write<W: std::io::Write>(
        self,
        writer: &mut Writer<W>,
        symbols: &SymbolSet,
    ) -> Result<()> {
        writer.write_event(Event::Start(BytesStart::new("parts").with_attributes([
            ("count", self.0.len().to_string().as_str()),
            ("current", "0"),
        ])))?;
        writer.get_mut().write_all(b"\n")?;

        for part in self.0 {
            part.write(writer, symbols)?;
            writer.get_mut().write_all(b"\n")?;
        }

        writer.write_event(Event::End(BytesEnd::new("parts")))?;
        Ok(())
    }

    pub(crate) fn parse<R: std::io::BufRead>(
        reader: &mut Reader<R>,
        symbols: &SymbolSet,
    ) -> Result<Self> {
        let mut parts = Vec::new();

        let mut buf = Vec::new();
        loop {
            match reader.read_event_into(&mut buf)? {
                Event::Start(bytes_start) => {
                    if matches!(bytes_start.local_name().as_ref(), b"part") {
                        parts.push(MapPart::parse(reader, &bytes_start, symbols)?);
                    }
                }
                Event::End(_) => break,
                Event::Eof => {
                    return Err(Error::ParseOmapFileError(
                        "Unexpected EOF in parsing MapParts".to_string(),
                    ));
                }
                _ => (),
            }
        }

        Ok(MapParts(parts))
    }
}