/*
 * Shapes:
 *   layer, dtype (2x i16)
 *   bounds (4x i32)
 *   offset/ptr (usize)
 *   cell_id (usize)
 *
 * Cell:
 *  name (??)
 *  offset (usize)
 *  len (usize)
 *
 * Refs:
 *   name_ind (usize)
 *   invert (bool)
 *   mag (f64)
 *   angle (f64)
 *   offset/ptr (usize)
 *
 */


use arrow;
use arrow::array::{LargeStringArray, DictionaryArray, UInt64Array, Int32Array, UInt16Array}
use arrow::builder::{LargeStringDictionaryBuilder, UInt64Builder, Int32Builder, Int64Builder, BooleanBuilder};
use library::FileHeader;

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];

#[derive(Debug, Clone)]
pub struct LibraryBuilder {
    cell_name: LargeStringDictionaryBuilder,
    cell_offset: UInt64Builder,

    ref_parent: UInt64Builder,
    ref_name: LargeStringDictionaryBuilder,
    ref_mirror: BooleanBuilder,
    ref_mag: Float64Builder,
    ref_angle: Float64Builder,
    ref_offset: UInt64Builder,

    shape_parent: UInt64Builder,
    shape_offset: UInt64Builder,
    shape_type: UInt8Builder,
    shape_layer: UInt16Builder,
    shape_dtype: UInt16Builder,
    shape_xmin: Int32Builder,
    shape_ymin: Int32Builder,
    shape_xmax: Int32Builder,
    shape_ymax: Int32Builder,
    shape_data: LargeListArrayBuilder<Int32Builder>,

    text_parent: UInt64Builder,
    text_string: LargeStringDictionaryBuilder,
    text_x: Int32Builder,
    text_y: Int32Builder,

    

    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)
    }
}



pub fn make_table(input: &[u8]) -> IResult<HashMap::<Vec<u8>, usize>> {
    let input_size = input.len();
    let mut names = LargeStringDictionaryBuilder::new();
    let mut positions = UInt64Builder::new();

    let (mut input, mut header) = RecordHeader::read(input)?;
    while header.tag != records::RTAG_ENDLIB {
        (input, let record_bytes) = take_bytes(input, header.data_size)?;
        if header.tag == records::RTAG_BGNSTR {
            let position = input_size - input.len();

            let name;
            (input, name) = records::STRNAME::read(record_bytes)?;

            names.append(name);
            positions.append(position);


            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, ()))
            }



        }
        (input, header) = RecordHeader::read(input)?;
    }
    Ok((input, positions))
}




pub struct References {
    invert_y: Vec<bool>,
    mag_and_angle_deg: Vec<f64>,
    xy: Vec<i32>,

    names: Vec<u8>,
    name_inds: Vec<usize>,

    has_prop: Vec<bool>,
    has_rep: Vec<bool>,
}

pub struct Repetitions {
    colrow_vecs: Vec<i32>,      // 4 per
    colrow_counts: Vec<i16>,    // 2 per
}

pub struct Shapes {
    layers_and_dtypes: Vec<i16>, // 2 per
    xy: Vec<i32>,  // variable per
    has_prop: Vec<bool>,
}



pub struct References {
    invert_y: bool,
    mag: f64,
    angle_deg: f64,

    xy: (i32, i32),

    // Use id to look up these... maybe include has_props and has_rep?
    struct_name: Vec<u8>,
    properties: HashMap::<i16, Vec<u8>>,

    col_vec: (i32 i32),
    row_vec: (i32, i32),
    colrow: (i16, i16),
}

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>>,
}

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>>,
}

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>>,
}
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>>,
}
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>>
}
