///
/// Functionality for reading/writing elements (geometry, text labels,
///  structure references) and associated properties.
///

use crate::records::{
    //BOX, BOUNDARY, NODE, PATH, TEXT, SREF, AREF,
    DATATYPE, PATHTYPE, BOXTYPE, NODETYPE, TEXTTYPE,
    LAYER, XY, WIDTH, COLROW, PRESENTATION, STRING,
    STRANS, MAG, ANGLE, PROPATTR, PROPVALUE,
    ENDEL, BGNEXTN, ENDEXTN, SNAME,
    };

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

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

use arrow::array::{
    StructBuilder, FixedSizeListBuilder, ListBuilder, StringBuilder, ArrayBuilder, BooleanBuilder,
    Int32Builder, Int16Builder, UInt32Builder, UInt8Builder, Float64Builder,
    };


pub type DListBuilder = ListBuilder<Box<dyn ArrayBuilder>>;
pub type FListBuilder = FixedSizeListBuilder<Box<dyn ArrayBuilder>>;


pub fn read_elements<'a>(input: &'a [u8], cell_builder: &mut StructBuilder, names: &mut HashMap<String, u32>) -> IResult<'a, ()> {
    let mut input = input;
    let (_, mut header) = RecordHeader::read(input)?;       // don't consume tag
    while header.tag != records::RTAG_ENDSTR {
        (input, _) = match header.tag {
            records::RTAG_BOUNDARY => read_boundary(input, cell_builder)?,
            records::RTAG_PATH => read_path(input, cell_builder)?,
            records::RTAG_NODE => read_boxnode(input, cell_builder, header.tag)?,
            records::RTAG_BOX => read_boxnode(input, cell_builder, header.tag)?,
            records::RTAG_TEXT => read_text(input, cell_builder)?,
            records::RTAG_SREF => read_ref(input, cell_builder, header.tag, names)?,
            records::RTAG_AREF => read_ref(input, cell_builder, header.tag, names)?,
            _ => {
                // don't care, skip
                let result = take_bytes(input, header.data_size)?;
                (result.0, ())
            }
        };
        (_, header) = RecordHeader::read(input)?;       // don't consume tag
        println!("{header:?}");
    }
    Ok((input, ()))
}

pub fn read_boundary<'a>(input: &'a [u8], cell_builder: &mut StructBuilder) -> IResult<'a, ()> {
    let boundaries_builder = cell_builder.field_builder::<DListBuilder>(3).unwrap();
    let boundary_builder = boundaries_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();

    let (input, _) = records::BOUNDARY::read(input)?;

    let (input, layer) = LAYER::skip_and_read(input)?;
    let layer_builder = boundary_builder.field_builder::<Int16Builder>(0).unwrap();
    layer_builder.append_value(layer);

    let (input, dtype) = DATATYPE::read(input)?;
    let dtype_builder = boundary_builder.field_builder::<Int16Builder>(1).unwrap();
    dtype_builder.append_value(dtype);

    let xys_builder = boundary_builder.field_builder::<DListBuilder>(2).unwrap();
    let xy_builder = xys_builder.values().as_any_mut().downcast_mut::<Int32Builder>().unwrap();
    let (input, xy_iter) = XY::read(input)?;
    for xy in xy_iter {
        xy_builder.append_value(xy);
    }
    xys_builder.append(true);

    let props_builder =  boundary_builder.field_builder::<DListBuilder>(3).unwrap();
    let (input, ()) = read_properties(input, props_builder)?;

    boundary_builder.append(true);
    boundaries_builder.append(true);
    Ok((input, ()))
}


pub fn read_path<'a>(input: &'a [u8], cell_builder: &mut StructBuilder) -> IResult<'a, ()> {
    let paths_builder = cell_builder.field_builder::<DListBuilder>(4).unwrap();
    let path_builder = paths_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();


    let (input, _) = records::PATH::read(input)?;

    let (input, layer) = LAYER::skip_and_read(input)?;
    let (input, dtype) = DATATYPE::read(input)?;
    let layer_builder = path_builder.field_builder::<Int16Builder>(0).unwrap();
    layer_builder.append_value(layer);
    let dtype_builder = path_builder.field_builder::<Int16Builder>(1).unwrap();
    dtype_builder.append_value(dtype);

    let mut path_type = None;
    let mut width = None;
    let mut bgn_ext = None;
    let mut end_ext = None;

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_XY {
        match header.tag {
            records::RTAG_PATHTYPE => {
                let _path_type;
                (input, _path_type) = PATHTYPE::read_data(input, header.data_size)?;
                path_type = Some(_path_type);
                },
            records::RTAG_WIDTH => {
                let _width;
                (input, _width) = WIDTH::read_data(input, header.data_size)?;
                width = Some(_width);
                },
            records::RTAG_BGNEXTN => {
                let _bgn_ext;
                (input, _bgn_ext) = BGNEXTN::read_data(input, header.data_size)?;
                bgn_ext = Some(_bgn_ext);
                },
            records::RTAG_ENDEXTN => {
                let _end_ext;
                (input, _end_ext) = ENDEXTN::read_data(input, header.data_size)?;
                end_ext = Some(_end_ext);
                },
            _ =>
                return fail(input, format!("Unexpected tag {:04x}", header.tag)),
        };
        (input, header) = RecordHeader::read(input)?;
    }
    let path_type_builder = path_builder.field_builder::<Int16Builder>(2).unwrap();
    path_type_builder.append_option(path_type);
    let ext0_builder = path_builder.field_builder::<Int32Builder>(3).unwrap();
    ext0_builder.append_option(bgn_ext);
    let ext1_builder = path_builder.field_builder::<Int32Builder>(4).unwrap();
    ext1_builder.append_option(end_ext);
    let width_builder = path_builder.field_builder::<Int32Builder>(5).unwrap();
    width_builder.append_option(width);

    let xys_builder = path_builder.field_builder::<DListBuilder>(6).unwrap();
    let (input, xy_iter) = XY::read(input)?;
    for xy in xy_iter {
        let xy_builder = xys_builder.values().as_any_mut().downcast_mut::<Int32Builder>().unwrap();
        xy_builder.append_value(xy);
    }
    xys_builder.append(true);

    let props_builder = path_builder.field_builder::<DListBuilder>(7).unwrap();
    let (input, ()) = read_properties(input, props_builder)?;

    path_builder.append(true);
    paths_builder.append(true);
    Ok((input, ()))
}

pub fn read_boxnode<'a>(input: &'a [u8], cell_builder: &mut StructBuilder, tag: u16) -> IResult<'a, ()> {
    let field_num = match tag {
        records::RTAG_NODE => 5,
        records::RTAG_BOX => 6,
        _ => return fail(input, format!("Unexpected tag {:04x}", tag)),
    };

    let boxnodes_builder = cell_builder.field_builder::<DListBuilder>(field_num).unwrap();
    let boxnode_builder = boxnodes_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();

    let (input, _) = match tag {
        records::RTAG_NODE => records::NODE::read(input)?,
        records::RTAG_BOX => records::BOX::read(input)?,
        _ => return fail(input, format!("Unexpected tag {:04x}", tag)),
    };

    let layer_builder = boxnode_builder.field_builder::<Int16Builder>(0).unwrap();
    let (input, layer) = LAYER::skip_and_read(input)?;
    layer_builder.append_value(layer);

    let (input, dtype) = match tag {
        records::RTAG_NODE => NODETYPE::read(input)?,
        records::RTAG_BOX => BOXTYPE::read(input)?,
        _ => return fail(input, format!("Unexpected tag {:04x}", tag)),
    };
    let dtype_builder = boxnode_builder.field_builder::<Int16Builder>(1).unwrap();
    dtype_builder.append_value(dtype);

    let xys_builder = boxnode_builder.field_builder::<DListBuilder>(2).unwrap();
    let xy_builder = xys_builder.values().as_any_mut().downcast_mut::<Int32Builder>().unwrap();
    let (input, xy_iter) = XY::read(input)?;
    for xy in xy_iter {
        xy_builder.append_value(xy);
    }
    xys_builder.append(true);

    let props_builder =  boxnode_builder.field_builder::<DListBuilder>(3).unwrap();
    let (input, ()) = read_properties(input, props_builder)?;

    boxnode_builder.append(true);
    boxnodes_builder.append(true);
    Ok((input, ()))
}

pub fn read_text<'a>(input: &'a [u8], cell_builder: &mut StructBuilder) -> IResult<'a, ()> {
    let texts_builder = cell_builder.field_builder::<DListBuilder>(7).unwrap();
    let text_builder = texts_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();


    let mut path_type = None;
    let mut pres_hori = None;
    let mut pres_vert = None;
    let mut pres_font = None;
    let mut invert_y = None;
    let mut width = None;
    let mut mag = None;
    let mut angle_deg = None;

    let (input, layer) = LAYER::skip_and_read(input)?;
    let layer_builder = text_builder.field_builder::<Int16Builder>(0).unwrap();
    layer_builder.append_value(layer);

    let (input, dtype) = TEXTTYPE::read(input)?;
    let dtype_builder = text_builder.field_builder::<Int16Builder>(1).unwrap();
    dtype_builder.append_value(dtype);

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_XY {
        match header.tag {
            // TODO warn if repeat tags?
            records::RTAG_PRESENTATION => {
                let _presentation;
                (input, _presentation) = PRESENTATION::read_data(input, header.data_size)?;
                pres_hori = Some(_presentation[14] as u8 * 2 + _presentation[15] as u8);
                pres_vert = Some(_presentation[12] as u8 * 2 + _presentation[13] as u8);
                pres_font = Some(_presentation[10] as u8 * 2 + _presentation[11] as u8);
                },
            records::RTAG_PATHTYPE => {
                let _path_type;
                (input, _path_type) = PATHTYPE::read_data(input, header.data_size)?;
                path_type = Some(_path_type);
                },
            records::RTAG_WIDTH => {
                let _width;
                (input, _width) = WIDTH::read_data(input, header.data_size)?;
                width = Some(_width);
                },
            records::RTAG_STRANS => {
                let strans;
                (input, strans) = STRANS::read_data(input, header.data_size)?;
                invert_y = Some(strans[0]);
            },
            records::RTAG_MAG => {
                let _mag;
                (input, _mag) = MAG::read_data(input, header.data_size)?;
                mag = Some(_mag);
                },
            records::RTAG_ANGLE => {
                let _angle_deg;
                (input, _angle_deg) = ANGLE::read_data(input, header.data_size)?;
                angle_deg = Some(_angle_deg);
                },
            _ =>
                return fail(input, format!("Unexpected tag {:04x}", header.tag)),
        }
        (input, header) = RecordHeader::read(input)?;
    }

    let pres_hori_builder = text_builder.field_builder::<UInt8Builder>(2).unwrap();
    pres_hori_builder.append_option(pres_hori);
    let pres_vert_builder = text_builder.field_builder::<UInt8Builder>(3).unwrap();
    pres_vert_builder.append_option(pres_vert);
    let pres_font_builder = text_builder.field_builder::<UInt8Builder>(4).unwrap();
    pres_font_builder.append_option(pres_font);
    let path_type_builder = text_builder.field_builder::<Int16Builder>(5).unwrap();
    path_type_builder.append_option(path_type);
    let width_builder = text_builder.field_builder::<Int32Builder>(6).unwrap();
    width_builder.append_option(width);
    let inv_builder =  text_builder.field_builder::<BooleanBuilder>(7).unwrap();
    inv_builder.append_option(invert_y);
    let mag_builder =  text_builder.field_builder::<Float64Builder>(8).unwrap();
    mag_builder.append_option(mag);
    let angle_builder =  text_builder.field_builder::<Float64Builder>(9).unwrap();
    angle_builder.append_option(angle_deg);

    let (input, mut xy_iter) = XY::read(input)?;
    let x_builder = text_builder.field_builder::<Int32Builder>(10).unwrap();
    x_builder.append_value(xy_iter.next().unwrap());
    let y_builder = text_builder.field_builder::<Int32Builder>(11).unwrap();
    y_builder.append_value(xy_iter.next().unwrap());

    let (input, string_bytes) = STRING::read(input)?;
    let string = String::from_utf8(string_bytes).unwrap();
    let string_builder = text_builder.field_builder::<StringBuilder>(12).unwrap();
    string_builder.append_value(string);

    let props_builder =  text_builder.field_builder::<DListBuilder>(13).unwrap();
    let (input, ()) = read_properties(input, props_builder)?;

    text_builder.append(true);
    texts_builder.append(true);
    Ok((input, ()))
}



pub fn read_ref<'a>(input: &'a [u8], cell_builder: &mut StructBuilder, tag: u16, names: &mut HashMap<String, u32>) -> IResult<'a, ()> {
    let (input, _) = match tag {
        records::RTAG_SREF => records::SREF::read(input)?,
        records::RTAG_AREF => records::AREF::read(input)?,
        _ => return fail(input, format!("Unexpected tag {:04x}", tag)),
    };

    let is_aref = tag == records::RTAG_AREF;
    let refs_builder = cell_builder.field_builder::<DListBuilder>(2).unwrap();
    let ref_builder = refs_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();

    let mut invert_y = None;
    let mut mag = None;
    let mut angle_deg = None;
    let mut colrow = None;

    let (input, struct_name_bytes) = SNAME::skip_and_read(input)?;
    let struct_name = String::from_utf8(struct_name_bytes).unwrap();
    let next_id = names.len();
    let id = names.entry(struct_name).or_insert(next_id.try_into().unwrap());
    let target_builder = ref_builder.field_builder::<UInt32Builder>(0).unwrap();
    target_builder.append_value(*id);

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_XY {
        match header.tag {
            records::RTAG_STRANS => {
                let strans;
                (input, strans) = STRANS::read_data(input, header.data_size)?;
                invert_y = Some(strans[0]);
                },
            records::RTAG_MAG => {
                let _mag;
                (input, _mag) = MAG::read_data(input, header.data_size)?;
                mag = Some(_mag);
                },
            records::RTAG_ANGLE => {
                let _angle_deg;
                (input, _angle_deg) = ANGLE::read_data(input, header.data_size)?;
                angle_deg = Some(_angle_deg);
                },
            records::RTAG_COLROW => {
                let mut _colrow;
                (input, _colrow) = COLROW::read_data(input, header.data_size)?;
                colrow = Some((_colrow.next().unwrap(), _colrow.next().unwrap()));
                if !is_aref {
                    return fail(input, "Got a COLROW record inside an SREF".to_string());
                }
                },
            _ =>
                return fail(input, format!("Unexpected tag {:04x}", header.tag)),
        };
        (input, header) = RecordHeader::read(input)?;
        println!("hi");
    }
    let inv_builder = ref_builder.field_builder::<BooleanBuilder>(1).unwrap();
    inv_builder.append_option(invert_y);
    let mag_builder = ref_builder.field_builder::<Float64Builder>(2).unwrap();
    mag_builder.append_option(mag);
    let angle_builder = ref_builder.field_builder::<Float64Builder>(3).unwrap();
    angle_builder.append_option(angle_deg);


    let (input, mut xy_iter) = XY::read_data(input, header.data_size)?;
    let x_builder = ref_builder.field_builder::<Int32Builder>(4).unwrap();
    x_builder.append_value(xy_iter.next().unwrap());
    let y_builder = ref_builder.field_builder::<Int32Builder>(5).unwrap();
    y_builder.append_value(xy_iter.next().unwrap());

    let rep_builder = ref_builder.field_builder::<StructBuilder>(6).unwrap();
    if is_aref {
        let x0_builder = rep_builder.field_builder::<Int32Builder>(0).unwrap();
        x0_builder.append_value(xy_iter.next().unwrap());
        let y0_builder = rep_builder.field_builder::<Int32Builder>(1).unwrap();
        y0_builder.append_value(xy_iter.next().unwrap());
        let x1_builder = rep_builder.field_builder::<Int32Builder>(2).unwrap();
        x1_builder.append_value(xy_iter.next().unwrap());
        let y1_builder = rep_builder.field_builder::<Int32Builder>(3).unwrap();
        y1_builder.append_value(xy_iter.next().unwrap());

        match colrow {
            None => return fail(input, "AREF without COLROW before XY".to_string()),
            Some((count0, count1)) => {
                let count0_builder = rep_builder.field_builder::<Int16Builder>(4).unwrap();
                count0_builder.append_value(count0);
                let count1_builder = rep_builder.field_builder::<Int16Builder>(5).unwrap();
                count1_builder.append_value(count1);
            },
        }
    }
    rep_builder.append(is_aref);

    let props_builder = ref_builder.field_builder::<DListBuilder>(7).unwrap();
    let (input, ()) = read_properties(input, props_builder)?;

    ref_builder.append(true);
    refs_builder.append(true);
    Ok((input, ()))
}


///
/// Read element properties.
///
/// Assumes PROPATTR records have unique values.
/// Stops reading after consuming ENDEL record.
///
/// Args:
///     stream: Stream to read from.
///
/// Returns:
///     propattr: -> propvalue mapping
///
pub fn read_properties<'a>(input: &'a [u8], props_builder: &mut DListBuilder) -> IResult<'a, ()> {
    let prop_builder = props_builder.values().as_any_mut().downcast_mut::<StructBuilder>().unwrap();

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != ENDEL::tag() {
        if header.tag == PROPATTR::tag() {
            let key;
            let value_bytes;
            (input, key) = PROPATTR::read_data(input, header.data_size)?;
            (input, value_bytes) = PROPVALUE::read(input)?;

            let value = String::from_utf8(value_bytes).unwrap();
            //assert!(!properties.contains_key(&key), "Duplicate property key: {}", key);

            let key_builder = prop_builder.field_builder::<Int16Builder>(0).unwrap();
            key_builder.append_value(key);

            let val_builder = prop_builder.field_builder::<StringBuilder>(1).unwrap();
            val_builder.append_value(value);

            prop_builder.append(true);
        }
        (input, header) = RecordHeader::read(input)?;
    }
    props_builder.append(true);
    Ok((input, ()))
}

/*

///
/// Write element properties.
///
/// This is does _not_ write the ENDEL record.
///
/// Args:
///     stream: Stream to write to.
///
pub fn write_properties<W: Write>(ww: &mut W, properties: &HashMap::<i16, Vec<u8>>) -> OResult {
    let mut size = 0;
    for (key, value) in properties {
        size += PROPATTR::write(ww, key)?;
        size += PROPVALUE::write(ww, value)?;
    }
    Ok(size)
}

pub trait Element {
    ///
    /// Read from a stream to construct this object.
    /// Consumes up to (and including) the ENDEL record.
    ///
    fn read(input: &[u8]) -> IResult<Self> where Self: Sized;

    ///
    /// Write this element to a stream.
    /// Finishes with an ENDEL record.
    ///
    fn write<W: Write>(&self, ww: &mut W) -> OResult;
}

///
/// Datastructure representing
///  an instance of a structure (SREF / structure reference) or
///  an array of instances      (AREF / array reference).
/// Type is determined by the presence of the `colrow` tuple.
///
/// Transforms are applied to each individual instance (_not_
///  to the instance's origin location || array vectors).
///
#[derive(Debug, Clone)]
pub struct Reference {
    /// Name of the structure being referenced.
    struct_name: Vec<u8>,
    /// Whether to mirror the pattern (negate y-values / flip across x-axis). Default false.
    invert_y: bool,
    /// Scaling factor (default 1)
    mag: f64,
    /// Rotation (degrees counterclockwise)
    angle_deg: f64,

    /// (For SREF) Location in the parent structure corresponding to the instance's origin (0, 0).
    /// (For AREF) 3 locations:
    ///           [`offset`,
    ///            `offset + col_basis_vector * colrow[0]`,
    ///            `offset + row_basis_vector * colrow[1]`]
    ///           which define the first instance's offset and the array's basis vectors.
    ///   Note that many GDS implementations only support manhattan basis vectors, and some
    ///     assume a certain axis mapping (e.g. x->columns, y->rows) and "reinterpret" the
    ///     basis vectors to match it.
    xy: Vec<i32>,

    /// Number of columns and rows (AREF) || None (SREF)
    colrow: Option<(i16, i16)>,
    /// Properties associated with this reference.
    properties: HashMap::<i16, Vec<u8>>,
}

impl Element for Reference {
    fn read(input: &[u8]) -> IResult<Self> {
        let mut invert_y = false;
        let mut mag = 1.0;
        let mut angle_deg = 0.0;
        let mut colrow = None;
        let (input, struct_name) = SNAME::skip_and_read(input)?;

        let (mut input, mut header) = RecordHeader::read(input)?;
        while header.tag != records::RTAG_XY {
            match header.tag {
                records::RTAG_STRANS => {
                    let result = STRANS::read_data(input, header.data_size)?;
                    input = result.0;
                    invert_y = result.1[0];
                    },
                records::RTAG_MAG =>
                    {(input, mag) = MAG::read_data(input, header.data_size)?;},
                records::RTAG_ANGLE =>
                    {(input, angle_deg) = ANGLE::read_data(input, header.data_size)?;},
                records::RTAG_COLROW => {
                    let result = COLROW::read_data(input, header.data_size)?;
                    input = result.0;
                    colrow = Some((result.1[0], result.1[1]));
                    },
                _ =>
                    return fail(input, format!("Unexpected tag {:04x}", header.tag)),
            };
            (input, header) = RecordHeader::read(input)?;
        }
        let (input, xy) = XY::read_data(input, header.data_size)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, Reference{
            struct_name: struct_name,
            xy: xy,
            properties: properties,
            colrow: colrow,
            invert_y: invert_y,
            mag: mag,
            angle_deg: angle_deg
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += match self.colrow {
            None => SREF::write(ww, &())?,
            Some(_) => AREF::write(ww, &())?,
        };

        size += SNAME::write(ww, &self.struct_name)?;
        if self.angle_deg != 0.0 || self.mag != 1.0 || self.invert_y {
            let strans = {
                let mut arr = [false; 16];
                arr[0] = self.invert_y;
                arr
            };
            size += STRANS::write(ww, &strans)?;
            if self.mag != 1.0 {
                size += MAG::write(ww, &self.mag)?;
            }
            if self.angle_deg != 0.0 {
                size += ANGLE::write(ww, &self.angle_deg)?;
            }
        }

        if let Some(cr) = self.colrow {
            size += COLROW::write(ww, &vec!{cr.0, cr.1})?;
        }

        size += XY::write(ww, &self.xy)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}

impl Reference {
    pub fn check(&self) {
        if self.colrow.is_some() {
            assert!(self.xy.len() != 6, "colrow is Some, so expected size-6 xy. Got {:?}", self.xy);
        } else {
            assert!(self.xy.len() != 2, "Expected size-2 xy. Got {:?}", self.xy);
        }
    }
}


///
/// Datastructure representing a Boundary element.
///
#[derive(Debug, Clone)]
pub struct Boundary {
    /// (layer, data_type) tuple
    layer: (i16, i16),
    /// Ordered vertices of the shape. First and last points should be identical. Order x0, y0, x1,...
    xy: Vec<i32>,
    /// Properties for the element.
    properties: HashMap::<i16, Vec<u8>>,
}

impl Element for Boundary {
    fn read(input: &[u8]) -> IResult<Self> {
        let (input, layer) = LAYER::skip_and_read(input)?;
        let (input, dtype) = DATATYPE::read(input)?;
        let (input, xy) = XY::read(input)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, Boundary{
            layer: (layer, dtype),
            xy: xy,
            properties: properties,
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += BOUNDARY::write(ww, &())?;
        size += LAYER::write(ww, &self.layer.0)?;
        size += DATATYPE::write(ww, &self.layer.1)?;
        size += XY::write(ww, &self.xy)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}


///
/// Datastructure representing a Path element.
///
/// If `path_type < 4`, `extension` values are not written.
/// During read, `exension` defaults to (0, 0) even if unused.
///
#[derive(Debug, Clone)]
pub struct Path {
    /// (layer, data_type) tuple
    layer: (i16, i16),
    /// End-cap type (0: flush, 1: circle, 2: square, 4: custom)
    path_type: i16,
    /// Path width
    width: i32,
    /// Extension when using path_type=4. Ignored otherwise.
    extension: (i32, i32),
    /// Path centerline coordinates. [x0, y0, x1, y1,...]
    xy: Vec<i32>,
    /// Properties for the element.
    properties: HashMap::<i16, Vec<u8>>,
}

impl Element for Path {
    fn read(input: &[u8]) -> IResult<Self> {
        let mut path_type = 0;
        let mut width = 0;
        let mut bgn_ext = 0;
        let mut end_ext = 0;
        let (input, layer) = LAYER::skip_and_read(input)?;
        let (input, dtype) = DATATYPE::read(input)?;

        let (mut input, mut header) = RecordHeader::read(input)?;
        while header.tag != records::RTAG_XY {
            match header.tag {
                records::RTAG_PATHTYPE =>
                    {(input, path_type) = PATHTYPE::read_data(input, header.data_size)?;},
                records::RTAG_WIDTH =>
                    {(input, width) = WIDTH::read_data(input, header.data_size)?;},
                records::RTAG_BGNEXTN =>
                    {(input, bgn_ext) = BGNEXTN::read_data(input, header.data_size)?;},
                records::RTAG_ENDEXTN =>
                    {(input, end_ext) = ENDEXTN::read_data(input, header.data_size)?;},
                _ =>
                    return fail(input, format!("Unexpected tag {:04x}", header.tag)),
            };
            (input, header) = RecordHeader::read(input)?;
        }
        let (input, xy) = XY::read_data(input, header.data_size)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, Path{
            layer: (layer, dtype),
            xy: xy,
            properties: properties,
            extension: (bgn_ext, end_ext),
            path_type: path_type,
            width: width,
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += PATH::write(ww, &())?;
        size += LAYER::write(ww, &self.layer.0)?;
        size += DATATYPE::write(ww, &self.layer.1)?;
        if self.path_type != 0 {
            size += PATHTYPE::write(ww, &self.path_type)?;
        }
        if self.width != 0 {
            size += WIDTH::write(ww, &self.width)?;
        }

        if self.path_type < 4 {
            let (bgn_ext, end_ext) = self.extension;
            if bgn_ext != 0 {
                size += BGNEXTN::write(ww, &bgn_ext)?;
            }
            if end_ext != 0 {
                size += ENDEXTN::write(ww, &end_ext)?;
            }
        }
        size += XY::write(ww, &self.xy)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}


///
/// Datastructure representing a Box element. Rarely used.
///
#[derive(Debug, Clone)]
pub struct GDSBox {
    /// (layer, box_type) tuple
    layer: (i16, i16),
    /// Box coordinates (5 pairs)
    xy: Vec<i32>,
    /// Properties for the element.
    properties: HashMap::<i16, Vec<u8>>,
}

impl Element for GDSBox {
    fn read(input: &[u8]) -> IResult<Self> {
        let (input, layer) = LAYER::skip_and_read(input)?;
        let (input, dtype) = BOXTYPE::read(input)?;
        let (input, xy) = XY::read(input)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, GDSBox{
            layer: (layer, dtype),
            xy: xy,
            properties: properties,
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += BOX::write(ww, &())?;
        size += LAYER::write(ww, &self.layer.0)?;
        size += BOXTYPE::write(ww, &self.layer.1)?;
        size += XY::write(ww, &self.xy)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}


///
/// Datastructure representing a Node element. Rarely used.
///
#[derive(Debug, Clone)]
pub struct Node {
    /// (layer, box_type) tuple
    layer: (i16, i16),
    /// 1-50 pairs of coordinates.
    xy: Vec<i32>,
    /// Properties for the element.
    properties: HashMap::<i16, Vec<u8>>,
}

impl Element for Node {
    fn read(input: &[u8]) -> IResult<Self> {
        let (input, layer) = LAYER::skip_and_read(input)?;
        let (input, dtype) = NODETYPE::read(input)?;
        let (input, xy) = XY::read(input)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, Node{
            layer: (layer, dtype),
            xy: xy,
            properties: properties,
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += NODE::write(ww, &())?;
        size += LAYER::write(ww, &self.layer.0)?;
        size += NODETYPE::write(ww, &self.layer.1)?;
        size += XY::write(ww, &self.xy)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}

///
/// Datastructure representing a text label.
///
#[derive(Debug, Clone)]
pub struct Text {
    /// (layer, node_type) tuple
    layer: (i16, i16),

    /// Bit array. Default all zeros.
    ///  bits 0-1: 00 left/01 center/10 right
    ///  bits 2-3: 00 top/01 middle/10 bottom
    ///  bits 4-5: font number
    presentation: [bool; 16],

    /// Default 0
    path_type: i16,
    /// Default 0
    width: i32,
    /// Vertical inversion. Default false.
    invert_y: bool,
    /// Scaling factor. Default 1.
    mag: f64,
    /// Rotation (ccw). Default 0.
    angle_deg: f64,
    /// Position (1 pair only)
    xy: Vec<i32>,
    /// Text content
    string: Vec<u8>,
    /// Properties for the element.
    properties: HashMap::<i16, Vec<u8>>
}

impl Element for Text {
    fn read(input: &[u8]) -> IResult<Self> {
        let mut path_type = 0;
        let mut presentation = [false; 16];
        let mut invert_y = false;
        let mut width = 0;
        let mut mag = 1.0;
        let mut angle_deg = 0.0;
        let (input, layer) = LAYER::skip_and_read(input)?;
        let (input, dtype) = TEXTTYPE::read(input)?;

        let (mut input, mut header) = RecordHeader::read(input)?;
        while header.tag != records::RTAG_XY {
            match header.tag {
                records::RTAG_PRESENTATION =>
                    {(input, presentation) = PRESENTATION::read_data(input, header.data_size)?;},
                records::RTAG_PATHTYPE =>
                    {(input, path_type) = PATHTYPE::read_data(input, header.data_size)?;},
                records::RTAG_WIDTH =>
                    {(input, width) = WIDTH::read_data(input, header.data_size)?;},
                records::RTAG_STRANS => {
                    let result = STRANS::read_data(input, header.data_size)?;
                    input = result.0;
                    invert_y = result.1[0];
                },
                records::RTAG_MAG =>
                    {(input, mag) = MAG::read_data(input, header.data_size)?;},
                records::RTAG_ANGLE =>
                    {(input, angle_deg) = ANGLE::read_data(input, header.data_size)?;},
                _ =>
                    return fail(input, format!("Unexpected tag {:04x}", header.tag)),
            }
            (input, header) = RecordHeader::read(input)?;
        }
        let (input, xy) = XY::read_data(input, header.data_size)?;

        let (input, string) = STRING::read(input)?;
        let (input, properties) = read_properties(input)?;
        Ok((input, Text{
            layer: (layer, dtype),
            xy: xy,
            properties: properties,
            string: string,
            presentation: presentation,
            path_type: path_type,
            width: width,
            invert_y: invert_y,
            mag: mag,
            angle_deg: angle_deg,
        }))
    }

    fn write<W: Write>(&self, ww: &mut W) -> OResult {
        let mut size = 0;
        size += TEXT::write(ww, &())?;
        size += LAYER::write(ww, &self.layer.0)?;
        size += TEXTTYPE::write(ww, &self.layer.1)?;
        if self.presentation.iter().any(|&x| x) {
            size += PRESENTATION::write(ww, &self.presentation)?;
        }
        if self.path_type != 0 {
            size += PATHTYPE::write(ww, &self.path_type)?;
        }
        if self.width != 0 {
            size += WIDTH::write(ww, &self.width)?
        }
        if self.angle_deg != 0.0 || self.mag != 1.0 || self.invert_y {
            let strans = {
                let mut arr = [false; 16];
                arr[0] = self.invert_y;
                arr
            };
            size += STRANS::write(ww, &strans)?;
            if self.mag != 1.0 {
                size += MAG::write(ww, &self.mag)?;
            }
            if self.angle_deg != 0.0 {
                size += ANGLE::write(ww, &self.angle_deg)?;
            }
        }
        size += XY::write(ww, &self.xy)?;
        size += STRING::write(ww, &self.string)?;
        size += write_properties(ww, &self.properties)?;
        size += ENDEL::write(ww, &())?;
        Ok(size)
    }
}
*/
