///
/// File-level read/write functionality.
///

use std::io::Write;
//use std::collections::HashMap;

pub use crate::record;
pub use crate::record::{RecordHeader, Record};
pub use crate::records;
pub use crate::elements;
//pub use crate::elements::{Element};
pub use crate::basic::{IResult, OResult, take_bytes, fail};


const DEFAULT_DATE: [i16; 6] = [1900, 0, 0, 0, 0, 0];


///
/// Representation of the GDS file header.
///
/// File header records: HEADER BGNLIB LIBNAME UNITS
///    Optional records are ignored if present and never written.
///
/// Version is written as `600`.
///
#[derive(Debug, Clone)]
pub struct FileHeader {
    /// Number of user units in one database unit
    user_units_per_db_unit: f64,
    /// Number of meters in one database unit
    meters_per_db_unit: f64,
    /// Last-modified time [y, m, d, h, m, s]
    mod_time: [i16; 6],
    /// Last-accessed time [y, m, d, h, m, s]
    acc_time: [i16; 6],
    /// Library name
    name: Vec<u8>,
}

impl FileHeader {
    pub fn new(name: &[u8], meters_per_db_unit: f64, user_units_per_db_unit: f64) -> Self {
        FileHeader{
            mod_time: [0, 1, 1, 0, 0, 0],
            acc_time: [0, 1, 1, 0, 0, 0],
            name: name.to_owned(),
            user_units_per_db_unit: user_units_per_db_unit,
            meters_per_db_unit: meters_per_db_unit,
        }
    }

    /// Read and construct a header from the provided input.
    ///
    /// Args:
    ///     input: Seekable input to read from
    ///
    /// Returns:
    ///     FileHeader object
    ///
    pub fn read(input: &[u8]) -> IResult<Self> {
        let (input, _version) = records::HEADER::read(input)?;
        let (input, [mod_time, acc_time]) = records::BGNLIB::read(input)?;
        let (input, name) = records::LIBNAME::skip_and_read(input)?;
        let (input, (uu, dbu)) = records::UNITS::skip_and_read(input)?;

        Ok((input, FileHeader{
            mod_time: mod_time,
            acc_time: acc_time,
            name: name,
            user_units_per_db_unit: uu,
            meters_per_db_unit: dbu,
        }))
    }

    /// Write the header to a input
    ///
    /// Args:
    ///     input: input to write to
    ///
    /// Returns:
    ///     number of bytes written
    ///
    pub fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += records::HEADER::write(ww, &600)?;
        size += records::BGNLIB::write(ww, &[self.mod_time, self.acc_time])?;
        size += records::LIBNAME::write(ww, &self.name)?;
        size += records::UNITS::write(ww, &(self.user_units_per_db_unit, self.meters_per_db_unit))?;
        Ok(size)
    }
}

/*
///
/// Scan through a GDS file, building a table of
///   {b'structure_name': byte_offset}.
/// The intent of this function is to enable random access
///  and/or partial (structure-by-structure) reads.
///
/// Args:
///     input: Seekable input to read from. Should be positioned
///             before the first structure record, but possibly
///             already past the file header.
///
pub fn scan_structs(input: &[u8]) -> IResult<HashMap::<Vec<u8>, usize>> {
    let input_size = input.len();
    let mut positions = HashMap::new();

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_ENDLIB {
        (input, _) = take_bytes(input, header.data_size)?;
        if header.tag == records::RTAG_BGNSTR {
            let name;
            (input, name) = records::STRNAME::read(input)?;
            if positions.contains_key(&name) {
                return fail(input, format!("Duplicate structure name: {:?}", name));
            }
            let position = input_size - input.len();
            positions.insert(name, position);
        }
        (input, header) = RecordHeader::read(input)?;
    }
    Ok((input, positions))
}
*/

/*
#[derive(Debug, Clone)]
pub struct Cell {
    name: Vec<u8>,

    boundaries: Vec<elements::Boundary>,
    paths: Vec<elements::Path>,
    nodes: Vec<elements::Node>,
    boxes: Vec<elements::GDSBox>,
    texts: Vec<elements::Text>,
    refs: Vec<elements::Reference>,
}

impl Cell {
    /// Build an empty cell
    pub fn new(name: Vec<u8>) -> Self {
        Cell{
            name: name,
            boundaries: Vec::new(),
            paths: Vec::new(),
            nodes: Vec::new(),
            boxes: Vec::new(),
            texts: Vec::new(),
            refs: Vec::new(),
        }
    }

    /// Skip to the next structure and attempt to read it.
    ///
    /// Args:
    ///     input: Seekable input to read from.
    ///
    /// Returns:
    ///     (name, elements) if a structure was found.
    ///     None if no structure was found before the end of the library.
    ///
    pub fn read(input: &[u8]) -> IResult<Option<Cell>> {
        let (input, success) = records::BGNSTR::skip_past(input)?;
        if !success {
            return Ok((input, None))
        }

        let (input, name) = records::STRNAME::read(input)?;
        let mut cell = Cell::new(name);
        let (input, _) = cell.read_elements(input)?;
        Ok((input, Some(cell)))
    }

    /// Read elements from the input until an ENDSTR
    ///   record is encountered. The ENDSTR record is also
    ///   consumed.
    ///
    /// Args:
    ///     input: Seekable input to read from.
    ///
    /// Returns:
    ///     List of element objects.
    ///
    pub fn read_elements<'a>(&mut self, input: &'a [u8]) -> IResult<'a, ()> {
        let (mut input, mut header) = RecordHeader::read(input)?;
        while header.tag != records::RTAG_ENDSTR {
            match header.tag {
                records::RTAG_BOUNDARY => {
                    let boundary;
                    (input, _) = records::BOUNDARY::read(input)?;
                    (input, boundary) = elements::Boundary::read(input)?;
                    self.boundaries.push(boundary);
                },
                records::RTAG_PATH => {
                    let path;
                    (input, _) = records::PATH::read(input)?;
                    (input, path) = elements::Path::read(input)?;
                    self.paths.push(path);
                },
                records::RTAG_NODE => {
                    let node;
                    (input, _) = records::NODE::read(input)?;
                    (input, node) = elements::Node::read(input)?;
                    self.nodes.push(node);
                },
                records::RTAG_BOX => {
                    let gds_box;
                    (input, _) = records::BOX::read(input)?;
                    (input, gds_box) = elements::GDSBox::read(input)?;
                    self.boxes.push(gds_box);
                },
                records::RTAG_TEXT => {
                    let txt;
                    (input, _) = records::TEXT::read(input)?;
                    (input, txt) = elements::Text::read(input)?;
                    self.texts.push(txt);
                },
                records::RTAG_SREF => {
                    let sref;
                    (input, _) = records::SREF::read(input)?;
                    (input, sref) = elements::Reference::read(input)?;
                    self.refs.push(sref);
                },
                records::RTAG_AREF => {
                    let aref;
                    (input, _) = records::AREF::read(input)?;
                    (input, aref) = elements::Reference::read(input)?;
                    self.refs.push(aref);
                },
                _ => {
                    // don't care, skip
                    (input, _) = take_bytes(input, header.data_size)?;
                }
            }
            (input, header) = RecordHeader::read(input)?;
        }
        Ok((input, ()))
    }

    ///
    /// Write a structure to the provided input.
    ///
    /// Args:
    ///     name: Structure name (ascii-encoded).
    ///     elements: List of Elements containing the geometry and text in this struct.
    ///     cre_time: Creation time (optional).
    ///     mod_time: Modification time (optional).
    ///
    /// Return:
    ///     Number of bytes written
    ///
    pub fn write<W: Write>(
            &self,
            ww: &mut W,
            cre_time: Option<[i16; 6]>,
            mod_time: Option<[i16; 6]>,
            ) -> OResult {
        let mut size = 0;
        size += records::BGNSTR::write(ww, &[cre_time.unwrap_or(DEFAULT_DATE),
                                             mod_time.unwrap_or(DEFAULT_DATE)])?;
        size += records::STRNAME::write(ww, &self.name)?;
        size += self.write_elements(ww)?;
        size += records::ENDSTR::write(ww, &())?;
        Ok(size)
    }

    pub fn write_elements<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        for boundary in &self.boundaries {
            size += boundary.write(ww)?;
        }
        for path in &self.paths {
            size += path.write(ww)?;
        }
        for node in &self.nodes {
            size += node.write(ww)?;
        }
        for gds_box in &self.boxes {
            size += gds_box.write(ww)?;
        }
        for text in &self.texts {
            size += text.write(ww)?;
        }
        for reference in &self.refs {
            size += reference.write(ww)?;
        }
        Ok(size)
    }
}
*/

/*
///
/// Scan through a GDS file, building a table of instance counts
///   `{b'structure_name': {b'ref_name': count}}`.
///
/// This is intended to provide a fast overview of the file's
///  contents without performing a full read of all elements.
///
/// Args:
///     input: Seekable input to read from. Should be positioned
///             before the first structure record, but possibly
///             already past the file header.
///
pub fn scan_hierarchy(input: &[u8]) -> IResult<HashMap::<Vec<u8>, HashMap::<Vec<u8>, u32>>> {
    let mut structures = HashMap::new();

    let mut ref_name = None;
    let mut ref_count = None;
    let (mut input, mut header) = RecordHeader::read(input)?;
    let mut cur_structure = HashMap::new();
    while header.tag != records::RTAG_ENDLIB {
        match header.tag {
            records::RTAG_BGNSTR => {
                (input, _) = take_bytes(input, header.data_size)?;
                let result = records::STRNAME::read(input)?;
                input = result.0;
                let name = result.1;
                if structures.contains_key(&name) {
                    return fail(input, format!("Duplicate structure name: {:?}", name));
                }
                let mut cur_structure = HashMap::new();
                structures.insert(name, cur_structure);
                ref_name = None;
                ref_count = None;
            },
            records::RTAG_SNAME => {
                let result = records::SNAME::read_data(input, header.data_size)?;
                input = result.0;
                ref_name = Some(result.1);
            },
            records::RTAG_COLROW => {
                let result = records::COLROW::read_data(input, header.data_size)?;
                input = result.0;
                let (col, row) = (result.1[0], result.1[1]);
                ref_count = Some((col * row) as u32);
            },
            records::RTAG_ENDEL => {
                *cur_structure.entry(ref_name.unwrap()).or_insert(0) += ref_count.unwrap_or(1);
            },
            _ => {
                (input, _) = take_bytes(input, header.data_size)?;
            },
        }
        (input, header) = RecordHeader::read(input)?;
    }
    Ok((input, structures))
}
*/

/*
pub fn count_ref(input: &[u8]) -> IResult<Option((Vec<u8>, u32))> {
    let (input, found_struc) = records::BGNSTR.skip_past(input)?;
    if !found_struc {
        return Ok((input, None))
    }
    let mut cur_structure = HashMap::new();
    let (input, name) = records::STRNAME::read(input)?;
    if structures.contains_key(&name) {
        return fail(input, format!("Duplicate structure name: {:?}", name));
    }

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_ENDSTR {
        let mut ref_name = None;
        let mut ref_count = None;
        while header.tag != records::RTAG_ENDEL {
            match header.tag {
                records::RTAG_SNAME => {
                    let result = records::SNAME::read_data(input1, header.data_size)?;
                    input1 = result.0;
                    ref_name = Some(result.1);
                },
                records::RTAG_COLROW => {
                    let result = records::COLROW::read_data(input1, header.data_size)?;
                    input1 = result.0;
                    let (col, row) = (result.1[0], result.1[1]);
                    ref_count = Some((col * row) as u32);
                },
                _ => {
                    (input1, _) = take_bytes(input1, header.data_size)?;
                },
            }
            (input1, header) = RecordHeader::read(input1)?;
        }
        // got ENDEL, update count for this reference
        *cur_structure.entry(ref_name.unwrap()).or_insert(0) += ref_count.unwrap_or(1);
        (input1, header) = RecordHeader::read(input1)?;
    }
    structures.insert(name, cur_structure);
    (input, header) = RecordHeader::read(input1)?;
}
*/