<commit_before>use super::sys;
use super::stringutils::{self, status_to_string};
use super::fitserror::{FitsError, Result};
use super::columndescription::ColumnDescription;
use super::conversions::typechar_to_data_type;
use super::libc;
use super::types::{DataType, CaseSensitivity, HduInfo, FileOpenMode, ImageType};
use std::ffi;
use std::ptr;
use std::ops::Range;

pub struct ImageDescription {
    pub data_type: ImageType,
    pub dimensions: Vec<usize>,
}

/// Main entry point to the FITS file format
///
///
pub struct FitsFile {
    pub filename: String,
    fptr: *const sys::fitsfile,
}

impl Clone for FitsFile {
    fn clone(&self) -> Self {
        FitsFile::open(self.filename.clone()).unwrap()
    }
}

impl FitsFile {
    /// Open a fits file from disk
    ///
    /// # Examples
    ///
    /// ```
    /// use fitsio::FitsFile;
    ///
    /// let f = FitsFile::open("../testdata/full_example.fits").unwrap();
    ///
    /// // Continue to use `f` afterwards
    /// ```
    pub fn open<T: Into<String>>(filename: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let filename = filename.into();
        let c_filename = ffi::CString::new(filename.as_str()).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        FileOpenMode::READONLY as libc::c_int,
                        &mut status);
        }

        fits_try!(status,
                  FitsFile {
                      fptr: fptr,
                      filename: filename.clone(),
                  })
    }

    /// Open a fits file in read/write mode
    pub fn edit<T: Into<String>>(filename: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let filename = filename.into();
        let c_filename = ffi::CString::new(filename.as_str()).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut _,
                        c_filename.as_ptr(),
                        FileOpenMode::READWRITE as libc::c_int,
                        &mut status);
        }

        fits_try!(status,
                  FitsFile {
                      fptr: fptr,
                      filename: filename.clone(),
                  })
    }

    /// Create a new fits file on disk
    pub fn create<T: Into<String>>(path: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let path = path.into();
        let c_filename = ffi::CString::new(path.as_str()).unwrap();

        unsafe {
            sys::ffinit(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        &mut status);
        }

        fits_try!(status, {
            let f = FitsFile {
                fptr: fptr,
                filename: path.clone(),
            };
            try!(f.add_empty_primary());
            f
        })
    }

    fn add_empty_primary(&self) -> Result<()> {
        let mut status = 0;
        unsafe {
            sys::ffphps(self.fptr as *mut _, 8, 0, ptr::null_mut(), &mut status);
        }

        fits_try!(status, ())
    }

    /// Change the current HDU
    fn change_hdu<T: DescribesHdu>(&self, hdu_description: T) -> Result<()> {
        hdu_description.change_hdu(self)
    }

    /// Return a new HDU object
    pub fn hdu<T: DescribesHdu>(&self, hdu_description: T) -> Result<FitsHdu> {
        FitsHdu::new(self, hdu_description)
    }

    fn hdu_number(&self) -> usize {
        let mut hdu_num = 0;
        unsafe {
            sys::ffghdn(self.fptr as *mut _, &mut hdu_num);
        }
        (hdu_num - 1) as usize
    }

    /// Get the current hdu as an HDU object
    pub fn current_hdu(&self) -> Result<FitsHdu> {
        let current_hdu_number = self.hdu_number();
        self.hdu(current_hdu_number)
    }

    /// Get the current hdu info
    fn fetch_hdu_info(&self) -> Result<HduInfo> {
        let mut status = 0;
        let mut hdu_type = 0;

        unsafe {
            sys::ffghdt(self.fptr as *mut _, &mut hdu_type, &mut status);
        }

        let hdu_type = match hdu_type {
            0 => {
                let mut dimensions = 0;
                unsafe {
                    sys::ffgidm(self.fptr as *mut _, &mut dimensions, &mut status);
                }

                let mut shape = vec![0; dimensions as usize];
                unsafe {
                    sys::ffgisz(self.fptr as *mut _,
                                dimensions,
                                shape.as_mut_ptr(),
                                &mut status);
                }

                HduInfo::ImageInfo { shape: shape.iter().map(|v| *v as usize).collect() }
            }
            1 | 2 => {
                let mut num_rows = 0;
                unsafe {
                    sys::ffgnrw(self.fptr as *mut _, &mut num_rows, &mut status);
                }

                let mut num_cols = 0;
                unsafe {
                    sys::ffgncl(self.fptr as *mut _, &mut num_cols, &mut status);
                }
                let mut column_descriptions = Vec::with_capacity(num_cols as usize);

                for i in 0..num_cols {
                    let mut name_buffer: Vec<libc::c_char> = vec![0; 71];
                    let mut type_buffer: Vec<libc::c_char> = vec![0; 71];
                    unsafe {
                        sys::ffgbcl(self.fptr as *mut _,
                                    (i + 1) as i32,
                                    name_buffer.as_mut_ptr(),
                                    ptr::null_mut(),
                                    type_buffer.as_mut_ptr(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    &mut status);
                    }

                    column_descriptions.push(ColumnDescription {
                        name: stringutils::buf_to_string(&name_buffer).unwrap(),
                        data_type: stringutils::buf_to_string(&type_buffer).unwrap(),
                    });
                }

                HduInfo::TableInfo {
                    column_descriptions: column_descriptions,
                    num_rows: num_rows as usize,
                }
            }
            _ => panic!("Invalid hdu type found"),
        };

        fits_try!(status, hdu_type)
    }

    pub fn create_table(&self,
                        extname: String,
                        table_description: &[ColumnDescription])
                        -> Result<FitsHdu> {
        let tfields = {
            let stringlist = table_description.iter()
                .map(|desc| desc.name.clone())
                .collect();
            stringutils::StringList::from_vec(stringlist)
        };

        let ttype = {
            let stringlist = table_description.iter()
                .map(|desc| desc.data_type.clone())
                .collect();
            stringutils::StringList::from_vec(stringlist)
        };

        let c_extname = ffi::CString::new(extname).unwrap();


        let hdu_info = HduInfo::TableInfo {
            column_descriptions: table_description.to_vec(),
            num_rows: 0,
        };

        let mut status: libc::c_int = 0;
        unsafe {
            sys::ffcrtb(self.fptr as *mut _,
                        hdu_info.into(),
                        0,
                        tfields.len as libc::c_int,
                        tfields.list,
                        ttype.list,
                        ptr::null_mut(),
                        c_extname.into_raw(),
                        &mut status);
        }

        if status != 0 {
            Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            })
        } else {
            self.current_hdu()
        }

    }

    pub fn create_image(&self,
                        extname: String,
                        image_description: &ImageDescription)
                        -> Result<FitsHdu> {
        let naxis = image_description.dimensions.len();
        let mut status = 0;
        unsafe {
            sys::ffcrim(self.fptr as *mut _,
                        image_description.data_type.into(),
                        naxis as i32,
                        image_description.dimensions.as_ptr() as *mut i64,
                        &mut status);
        }

        if status != 0 {
            return Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            });
        }

        // Current HDU should be at the new HDU
        let mut current_hdu = try!(self.current_hdu());
        try!(current_hdu.write_key("EXTNAME".into(), extname));

        if status != 0 {
            Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            })
        } else {
            self.current_hdu()
        }
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        let mut status = 0;
        unsafe {
            sys::ffclos(self.fptr as *mut _, &mut status);
        }
    }
}

/// Hdu description type
///
/// Any way of describing a HDU - number or string which either
/// changes the hdu by absolute number, or by name.
pub trait DescribesHdu {
    fn change_hdu(&self, fptr: &FitsFile) -> Result<()>;
}

impl DescribesHdu for usize {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        unsafe {
            sys::ffmahd(f.fptr as *mut _,
                        (*self + 1) as i32,
                        &mut _hdu_type,
                        &mut status);
        }

        fits_try!(status, ())
    }
}

impl<'a> DescribesHdu for &'a str {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        let c_hdu_name = ffi::CString::new(*self).unwrap();

        unsafe {
            sys::ffmnhd(f.fptr as *mut _,
                        HduInfo::AnyInfo.into(),
                        c_hdu_name.into_raw(),
                        0,
                        &mut status);
        }

        fits_try!(status, ())
    }
}

/// Trait for reading a fits column
pub trait ReadsCol {
    fn read_col<T: Into<String>>(fits_file: &FitsFile, name: T) -> Result<Vec<Self>>
        where Self: Sized;
    fn read_col_range<T: Into<String>>(fits_file: &FitsFile,
                                       name: T,
                                       range: &Range<usize>)
                                       -> Result<Vec<Self>>
        where Self: Sized;
}

macro_rules! reads_col_impl {
    ($t: ty, $func: ident, $nullval: expr) => (
        impl ReadsCol for $t {
            fn read_col<T: Into<String>>(fits_file: &FitsFile, name: T) -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::TableInfo {
                        column_descriptions, num_rows, ..
                    }) => {
                        let mut out = vec![$nullval; num_rows];
                        let test_name = name.into();
                        assert_eq!(out.len(), num_rows);
                        let column_number = column_descriptions.iter().position(|ref desc| {
                            desc.name == test_name
                        }).unwrap();
                        let mut status = 0;
                        unsafe {
                            sys::$func(fits_file.fptr as *mut _,
                                       (column_number + 1) as i32,
                                       1,
                                       1,
                                       num_rows as i64,
                                       $nullval,
                                       out.as_mut_ptr(),
                                       ptr::null_mut(),
                                       &mut status);

                        }
                        fits_try!(status, out)
                    },
                    Err(e) => Err(e),
                    _ => panic!("Unknown error occurred"),
                }
            }

            // TODO: should we check the bounds? cfitsio will raise an error, but we
            // could be more friendly and raise our own?
            fn read_col_range<T: Into<String>>(fits_file: &FitsFile, name: T, range: &Range<usize>)
                -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::TableInfo { column_descriptions, .. }) => {
                        let num_output_rows = range.end - range.start + 1;
                        let mut out = vec![$nullval; num_output_rows];
                        let test_name = name.into();
                        let column_number = column_descriptions.iter().position(|ref desc| {
                            desc.name == test_name
                        }).unwrap();
                        let mut status = 0;
                        unsafe {
                            sys::$func(fits_file.fptr as *mut _,
                                       (column_number + 1) as i32,
                                       (range.start + 1) as i64,
                                       1,
                                       num_output_rows as _,
                                       $nullval,
                                       out.as_mut_ptr(),
                                       ptr::null_mut(),
                                       &mut status);

                        }
                        fits_try!(status, out)
                    },
                    Err(e) => Err(e),
                    _ => panic!("Unknown error occurred"),
                }
            }
        }
    )
}

reads_col_impl!(i32, ffgcvk, 0);
reads_col_impl!(u32, ffgcvuk, 0);
reads_col_impl!(i64, ffgcvj, 0);
reads_col_impl!(u64, ffgcvuj, 0);
reads_col_impl!(f32, ffgcve, 0.0);
reads_col_impl!(f64, ffgcvd, 0.0);

// TODO: impl for string

pub trait WritesCol {
    fn write_col<T: Into<String>>(fits_file: &FitsFile,
                                  hdu: &FitsHdu,
                                  col_name: T,
                                  col_data: &[Self])
                                  -> Result<()>
        where Self: Sized;
    fn write_col_range<T: Into<String>>(fits_file: &FitsFile,
                                        hdu: &FitsHdu,
                                        col_name: T,
                                        col_data: &[Self],
                                        rows: &Range<usize>)
                                        -> Result<()>
        where Self: Sized;
}

macro_rules! writes_col_impl {
    ($t: ty, $data_type: expr) => (
        impl WritesCol for $t {
            fn write_col<T: Into<String>>(
                fits_file: &FitsFile,
                hdu: &FitsHdu,
                col_name: T,
                col_data: &[Self]) -> Result<()> {
                let colno = hdu.get_column_no(col_name.into())?;
                let mut status = 0;
                unsafe {
                    sys::ffpcl(
                        fits_file.fptr as *mut _,
                        $data_type.into(),
                        (colno + 1) as _,
                        1,
                        1,
                        col_data.len() as _,
                        col_data.as_ptr() as *mut _,
                        &mut status);
                }
                fits_try!(status, ())
            }

            fn write_col_range<T: Into<String>>(fits_file: &FitsFile,
                hdu: &FitsHdu,
                col_name: T,
                col_data: &[Self],
                rows: &Range<usize>)
            -> Result<()> {
                let colno = hdu.get_column_no(col_name.into())?;
                let mut status = 0;
                unsafe {
                    sys::ffpcl(
                        fits_file.fptr as *mut _,
                        $data_type.into(),
                        (colno + 1) as _,
                        (rows.start + 1) as _,
                        1,
                        (rows.end + 1) as _,
                        col_data.as_ptr() as *mut _,
                        &mut status
                    );
                }
                fits_try!(status, ())
            }
        }
    )
}

writes_col_impl!(u32, DataType::TUINT);
writes_col_impl!(u64, DataType::TULONG);
writes_col_impl!(i32, DataType::TINT);
writes_col_impl!(i64, DataType::TLONG);
writes_col_impl!(f32, DataType::TFLOAT);
writes_col_impl!(f64, DataType::TDOUBLE);

/// Trait applied to types which can be read from a FITS header
///
/// This is currently:
///
/// * i32
/// * i64
/// * f32
/// * f64
/// * String
pub trait ReadsKey {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> where Self: Sized;
}

macro_rules! reads_key_impl {
    ($t:ty, $func:ident) => (
        impl ReadsKey for $t {
            fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;
                let mut value: Self = Self::default();

                unsafe {
                    sys::$func(f.fptr as *mut _,
                           c_name.into_raw(),
                           &mut value,
                           ptr::null_mut(),
                           &mut status);
                }

                fits_try!(status, value)
            }
        }
    )
}

reads_key_impl!(i32, ffgkyl);
reads_key_impl!(i64, ffgkyj);
reads_key_impl!(f32, ffgkye);
reads_key_impl!(f64, ffgkyd);

impl ReadsKey for String {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;
        let mut value: Vec<libc::c_char> = vec![0; sys::MAX_VALUE_LENGTH];

        unsafe {
            sys::ffgkys(f.fptr as *mut _,
                        c_name.into_raw(),
                        value.as_mut_ptr(),
                        ptr::null_mut(),
                        &mut status);
        }

        fits_try!(status, {
            let value: Vec<u8> = value.iter()
                .map(|&x| x as u8)
                .filter(|&x| x != 0)
                .collect();
            String::from_utf8(value).unwrap()
        })
    }
}

/// Writing a fits keyword
pub trait WritesKey {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()>;
}

macro_rules! writes_key_impl_flt {
    ($t:ty, $func:ident) => (
        impl WritesKey for $t {
            fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;

                unsafe {
                    sys::$func(f.fptr as *mut _,
                                c_name.into_raw(),
                                value,
                                9,
                                ptr::null_mut(),
                                &mut status);
                }
                fits_try!(status, ())
            }
        }
    )
}

impl WritesKey for i64 {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;

        unsafe {
            sys::ffpkyj(f.fptr as *mut _,
                        c_name.into_raw(),
                        value,
                        ptr::null_mut(),
                        &mut status);
        }
        fits_try!(status, ())
    }
}

writes_key_impl_flt!(f32, ffpkye);
writes_key_impl_flt!(f64, ffpkyd);

impl WritesKey for String {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;

        unsafe {
            sys::ffpkys(f.fptr as *mut _,
                        c_name.into_raw(),
                        ffi::CString::new(value).unwrap().into_raw(),
                        ptr::null_mut(),
                        &mut status);
        }

        fits_try!(status, ())
    }
}

/// Reading fits images
pub trait ReadWriteImage: Sized {
    /// Read pixels from an image between a start index and end index
    ///
    /// Start and end are read inclusively, so start = 0, end = 10 will read 11 pixels
    /// in a row.
    fn read_section(fits_file: &FitsFile, start: usize, end: usize) -> Result<Vec<Self>>;

    /// Read a row of pixels from a fits image
    fn read_rows(fits_file: &FitsFile, start_row: usize, num_rows: usize) -> Result<Vec<Self>>;

    /// Read a single row from the image HDU
    fn read_row(fits_file: &FitsFile, row: usize) -> Result<Vec<Self>>;

    /// Read a square region from the chip.
    ///
    /// Lower left indicates the starting point of the square, and the upper
    /// right defines the pixel _beyond_ the end. The range of pixels included
    /// is inclusive of the lower end, and *exclusive* of the upper end.
    fn read_region(fits_file: &FitsFile, ranges: &[&Range<usize>]) -> Result<Vec<Self>>;

    /// Read a whole image into a new `Vec`
    ///
    /// This reads an entire image into a one-dimensional vector
    fn read_image(fits_file: &FitsFile) -> Result<Vec<Self>> {
        match fits_file.fetch_hdu_info() {
            Ok(HduInfo::ImageInfo { shape }) => {
                let mut npixels = 1;
                for dimension in &shape {
                    npixels *= *dimension;
                }
                Self::read_section(fits_file, 0, npixels)
            }
            Ok(HduInfo::TableInfo { .. }) => {
                Err(FitsError {
                    status: 601,
                    message: "cannot read image data from a table hdu".to_string(),
                })
            }
            Ok(HduInfo::AnyInfo) => unreachable!(),
            Err(e) => Err(e),
        }
    }

    fn write_section(fits_file: &FitsFile, start: usize, end: usize, data: &[Self]) -> Result<()>;

    fn write_region(fits_file: &FitsFile, ranges: &[&Range<usize>], data: &[Self]) -> Result<()>;
}

macro_rules! read_write_image_impl {
    ($t: ty, $data_type: expr) => (
        impl ReadWriteImage for $t {
            fn read_section(fits_file: &FitsFile, start: usize, end: usize) -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::ImageInfo { shape: _shape }) => {
                        let nelements = end - start;
                        let mut out = vec![0 as $t; nelements];
                        let mut status = 0;

                        unsafe {
                            sys::ffgpv(fits_file.fptr as *mut _,
                                       $data_type.into(),
                                       (start + 1) as i64,
                                       nelements as i64,
                                       ptr::null_mut(),
                                       out.as_mut_ptr() as *mut libc::c_void,
                                       ptr::null_mut(),
                                       &mut status);
                        }

                        fits_try!(status, out)

                    },
                    Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                        status: 601,
                        message: "cannot read image data from a table hdu".to_string(),
                    }),
                    Ok(HduInfo::AnyInfo) => unreachable!(),
                    Err(e) => Err(e),
                }
            }

            fn read_rows(fits_file: &FitsFile, start_row: usize, num_rows: usize)
                -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::ImageInfo { shape }) => {
                        if shape.len() != 2 {
                            unimplemented!();
                        }

                        let num_cols = shape[1];
                        let start = start_row * num_cols;
                        let end = (start_row + num_rows) * num_cols;

                        Self::read_section(fits_file, start, end)
                    },
                    Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                        status: 601,
                        message: "cannot read image data from a table hdu".to_string(),
                    }),
                    Ok(HduInfo::AnyInfo) => unreachable!(),
                    Err(e) => Err(e),
                }
            }

            fn read_row(fits_file: &FitsFile, row: usize) -> Result<Vec<Self>> {
                Self::read_rows(fits_file, row, 1)
            }

            fn read_region(fits_file: &FitsFile, ranges: &[&Range<usize>])
                -> Result<Vec<Self>> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { shape }) => {
                            if shape.len() != 2 {
                                unimplemented!();
                            }

                            if ranges.len() != 2 {
                                unimplemented!();
                            }

                            // These have to be mutable because of the C-api
                            let mut fpixel = [
                                (ranges[0].start + 1) as _,
                                (ranges[1].start + 1) as _
                            ];
                            let mut lpixel = [
                                (ranges[0].end + 1) as _,
                                (ranges[1].end + 1) as _
                            ];

                            let mut inc = [ 1, 1 ];
                            let nelements =
                                ((lpixel[0] - fpixel[0]) + 1) * ((lpixel[1] - fpixel[1]) + 1);
                            let mut out = vec![0 as $t; nelements as usize];
                            let mut status = 0;

                            unsafe {
                                sys::ffgsv(
                                    fits_file.fptr as *mut _,
                                    $data_type.into(),
                                    fpixel.as_mut_ptr(),
                                    lpixel.as_mut_ptr(),
                                    inc.as_mut_ptr(),
                                    ptr::null_mut(),
                                    out.as_mut_ptr() as *mut libc::c_void,
                                    ptr::null_mut(),
                                    &mut status);

                            }

                            fits_try!(status, out)
                        }
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot read image data from a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }

            fn write_section(
                fits_file: &FitsFile,
                start: usize,
                end: usize,
                data: &[Self])
                -> Result<()> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { .. }) => {
                            let nelements = end - start;
                            assert!(data.len() >= nelements);
                            let mut status = 0;
                            unsafe {
                                sys::ffppr(fits_file.fptr as *mut _,
                                           $data_type.into(),
                                           (start + 1) as i64,
                                           nelements as i64,
                                           data.as_ptr() as *mut _,
                                           &mut status);
                            }

                            fits_try!(status, ())
                        },
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot write image data to a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }

            fn write_region(
                fits_file: &FitsFile,
                ranges: &[&Range<usize>],
                data: &[Self])
                -> Result<()> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { .. }) => {
                            let mut fpixel = [
                                (ranges[0].start + 1) as _,
                                (ranges[1].start + 1) as _
                            ];
                            let mut lpixel = [
                                (ranges[1].end + 1) as _,
                                (ranges[1].end + 1) as _
                            ];
                            let mut status = 0;

                            unsafe {
                                sys::ffpss(
                                    fits_file.fptr as *mut _,
                                    $data_type.into(),
                                    fpixel.as_mut_ptr(),
                                    lpixel.as_mut_ptr(),
                                    data.as_ptr() as *mut libc::c_void,
                                    &mut status);
                            }

                            fits_try!(status, ())
                        },
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot write image data to a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }
        }
    )
}


read_write_image_impl!(i8, DataType::TSHORT);
read_write_image_impl!(i32, DataType::TINT);
read_write_image_impl!(i64, DataType::TLONG);
read_write_image_impl!(u8, DataType::TUSHORT);
read_write_image_impl!(u32, DataType::TUINT);
read_write_image_impl!(u64, DataType::TULONG);
read_write_image_impl!(f32, DataType::TFLOAT);
read_write_image_impl!(f64, DataType::TDOUBLE);

pub enum Column {
    Int32 { name: String, data: Vec<i32> },
    Int64 { name: String, data: Vec<i64> },
    Float { name: String, data: Vec<f32> },
    Double { name: String, data: Vec<f64> },
}

pub struct ColumnIterator<'a> {
    current: usize,
    column_descriptions: Vec<ColumnDescription>,
    fits_file: &'a FitsFile,
}

impl<'a> ColumnIterator<'a> {
    fn new(fits_file: &'a FitsFile) -> Self {
        match fits_file.fetch_hdu_info() {
            Ok(HduInfo::TableInfo { column_descriptions, num_rows: _num_rows }) => {
                ColumnIterator {
                    current: 0,
                    column_descriptions: column_descriptions,
                    fits_file: fits_file,
                }
            }
            Err(e) => panic!("{:?}", e),
            _ => panic!("Unknown error occurred"),
        }
    }
}

impl<'a> Iterator for ColumnIterator<'a> {
    type Item = Column;

    fn next(&mut self) -> Option<Self::Item> {
        let ncols = self.column_descriptions.len();

        if self.current < ncols {
            let description = &self.column_descriptions[self.current];
            let current_name = description.name.as_str();
            let current_type = typechar_to_data_type(description.data_type.as_str());

            let retval = match current_type {
                DataType::TSHORT => {
                    i32::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Int32 {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TLONG => {
                    i64::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Int64 {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TFLOAT => {
                    f32::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Float {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TDOUBLE => {
                    f64::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Double {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                _ => unimplemented!(),
            };

            self.current += 1;

            retval

        } else {
            None
        }
    }
}

pub struct FitsHdu<'open> {
    fits_file: &'open FitsFile,
    pub info: HduInfo,
}

impl<'open> FitsHdu<'open> {
    pub fn new<T: DescribesHdu>(fits_file: &'open FitsFile, hdu_description: T) -> Result<Self> {
        try!(fits_file.change_hdu(hdu_description));
        match fits_file.fetch_hdu_info() {
            Ok(hdu_info) => {
                Ok(FitsHdu {
                    fits_file: fits_file,
                    info: hdu_info,
                })
            }
            Err(e) => Err(e),
        }
    }

    /// Read header key
    pub fn read_key<T: ReadsKey>(&self, name: &str) -> Result<T> {
        T::read_key(self.fits_file, name)
    }

    /// Write header key
    pub fn write_key<T: WritesKey>(&mut self, name: &str, value: T) -> Result<()> {
        T::write_key(self.fits_file, name, value)
    }

    /// Read an image between pixel a and pixel b into a `Vec`
    pub fn read_section<T: ReadWriteImage>(&self, start: usize, end: usize) -> Result<Vec<T>> {
        T::read_section(self.fits_file, start, end)
    }

    /// Read multiple rows from a fits image
    pub fn read_rows<T: ReadWriteImage>(&self,
                                        start_row: usize,
                                        num_rows: usize)
                                        -> Result<Vec<T>> {
        T::read_rows(self.fits_file, start_row, num_rows)
    }

    /// Read a single row from a fits image
    pub fn read_row<T: ReadWriteImage>(&self, row: usize) -> Result<Vec<T>> {
        T::read_row(self.fits_file, row)
    }

    /// Read a whole fits image into a vector
    pub fn read_image<T: ReadWriteImage>(&self) -> Result<Vec<T>> {
        T::read_image(self.fits_file)
    }

    /// Write contiguous data to a fits image
    pub fn write_section<T: ReadWriteImage>(&mut self,
                                            start: usize,
                                            end: usize,
                                            data: &[T])
                                            -> Result<()> {
        T::write_section(self.fits_file, start, end, data)
    }

    /// Write a rectangular region to a fits image
    pub fn write_region<T: ReadWriteImage>(&mut self,
                                           ranges: &[&Range<usize>],
                                           data: &[T])
                                           -> Result<()> {
        T::write_region(self.fits_file, ranges, data)
    }

    /// Read a square region into a `Vec`
    pub fn read_region<T: ReadWriteImage>(&self, ranges: &[&Range<usize>]) -> Result<Vec<T>> {
        T::read_region(self.fits_file, ranges)
    }

    pub fn get_column_no<T: Into<String>>(&self, col_name: T) -> Result<usize> {
        let mut status = 0;
        let mut colno = 0;

        let c_col_name = {
            let col_name = col_name.into();
            ffi::CString::new(col_name.as_str()).unwrap()
        };

        unsafe {
            sys::ffgcno(self.fits_file.fptr as *mut _,
                        CaseSensitivity::CASEINSEN as _,
                        c_col_name.as_ptr() as *mut _,
                        &mut colno,
                        &mut status);
        }
        fits_try!(status, (colno - 1) as usize)
    }

    /// Read a binary table column
    pub fn read_col<T: ReadsCol>(&self, name: &str) -> Result<Vec<T>> {
        T::read_col(self.fits_file, name)
    }

    pub fn read_col_range<T: ReadsCol>(&self, name: &str, range: &Range<usize>) -> Result<Vec<T>> {
        T::read_col_range(self.fits_file, name, range)
    }

    pub fn write_col<T: WritesCol, N: Into<String>>(&mut self,
                                                    name: N,
                                                    col_data: &[T])
                                                    -> Result<()> {
        T::write_col(self.fits_file, self, name, col_data)
    }

    pub fn write_col_range<T: WritesCol, N: Into<String>>(&mut self,
                                                          name: N,
                                                          col_data: &[T],
                                                          rows: &Range<usize>)
                                                          -> Result<()> {
        T::write_col_range(self.fits_file, self, name, col_data, rows)
    }

    pub fn columns(&self) -> ColumnIterator {
        ColumnIterator::new(self.fits_file)
    }
}


#[cfg(test)]
mod test {
    extern crate tempdir;

    use super::FitsHdu;
    use super::super::fitsfile::FitsFile;
    use super::super::types::HduInfo;
    use super::super::types::*;
    use super::ImageDescription;
    use fitserror::FitsError;
    use conversions::typechar_to_data_type;
    use std::{f32, f64};

    // FitsFile tests
    #[test]
    fn typechar_conversions() {
        let input = vec!["X", "B", "L", "A", "I", "J", "E", "D", "C", "M"];
        let expected = vec![DataType::TBIT,
                            DataType::TBYTE,
                            DataType::TLOGICAL,
                            DataType::TSTRING,
                            DataType::TSHORT,
                            DataType::TLONG,
                            DataType::TFLOAT,
                            DataType::TDOUBLE,
                            DataType::TCOMPLEX,
                            DataType::TDBLCOMPLEX];

        for (i, e) in input.iter().zip(expected) {
            assert_eq!(typechar_to_data_type(i), e);
        }
    }

    #[test]
    fn opening_an_existing_file() {
        match FitsFile::open("../testdata/full_example.fits") {
            Ok(_) => {}
            Err(e) => panic!("{:?}", e),
        }
    }

    #[test]
    fn creating_a_new_file() {
        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");
        assert!(!filename.exists());

        FitsFile::create(filename.to_str().unwrap())
            .map(|f| {
                assert!(filename.exists());

                // Ensure the empty primary has been written
                let naxis: i64 = f.hdu(0)
                    .unwrap()
                    .read_key("NAXIS")
                    .unwrap();
                assert_eq!(naxis, 0);
            })
            .unwrap();
    }

    #[test]
    fn editing_a_current_file() {
        use std::fs;

        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");

        fs::copy("../testdata/full_example.fits", &filename).expect("Could not copy test file");

        {
            let f = FitsFile::edit(filename.to_str().unwrap()).unwrap();
            let mut image_hdu = f.hdu(0).unwrap();

            let data_to_write: Vec<i64> = (0..100).map(|_| 10101).collect();
            image_hdu.write_section(0, 100, &data_to_write).unwrap();
        }

        {
            let f = FitsFile::open(filename.to_str().unwrap()).unwrap();
            let hdu = f.hdu(0).unwrap();
            let read_data: Vec<i64> = hdu.read_section(0, 10).unwrap();
            assert_eq!(read_data, vec![10101; 10]);
        }
    }

    #[test]
    fn fetching_a_hdu() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        for i in 0..2 {
            f.change_hdu(i).unwrap();
            assert_eq!(f.hdu_number(), i);
        }

        match f.change_hdu(2) {
            Err(e) => assert_eq!(e.status, 107),
            _ => panic!("Error checking for failure"),
        }

        f.change_hdu("TESTEXT").unwrap();
        assert_eq!(f.hdu_number(), 1);
    }

    #[test]
    fn fetching_hdu_info() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        match f.fetch_hdu_info() {
            Ok(HduInfo::ImageInfo { shape }) => {
                assert_eq!(shape.len(), 2);
                assert_eq!(shape, vec![100, 100]);
            }
            Err(e) => panic!("Error fetching hdu info {:?}", e),
            _ => panic!("Unknown error"),
        }

        f.change_hdu(1).unwrap();
        match f.fetch_hdu_info() {
            Ok(HduInfo::TableInfo { column_descriptions, num_rows }) => {
                assert_eq!(num_rows, 50);
                assert_eq!(column_descriptions.iter()
                               .map(|desc| desc.name.clone())
                               .collect::<Vec<String>>(),
                           vec!["intcol".to_string(),
                                "floatcol".to_string(),
                                "doublecol".to_string()]);
                assert_eq!(column_descriptions.iter()
                               .map(|ref desc| typechar_to_data_type(desc.data_type.clone()))
                               .collect::<Vec<DataType>>(),
                           vec![DataType::TLONG, DataType::TFLOAT, DataType::TDOUBLE]);
            }
            Err(e) => panic!("Error fetching hdu info {:?}", e),
            _ => panic!("Unknown error"),
        }
    }

    #[test]
    fn cloning() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        let f2 = f.clone();

        assert!(f.fptr != f2.fptr);

        f.change_hdu(1).unwrap();
        assert!(f.hdu_number() != f2.hdu_number());
    }

    #[test]
    fn test_fits_try() {
        use stringutils;

        let status = 0;
        assert_eq!(fits_try!(status, 10), Ok(10));

        let status = 105;
        assert_eq!(fits_try!(status, 10),
                   Err(FitsError {
                       status: status,
                       message: stringutils::status_to_string(status).unwrap(),
                   }));
    }<commit_msg>Remove the ability to clone<commit_after>use super::sys;
use super::stringutils::{self, status_to_string};
use super::fitserror::{FitsError, Result};
use super::columndescription::ColumnDescription;
use super::conversions::typechar_to_data_type;
use super::libc;
use super::types::{DataType, CaseSensitivity, HduInfo, FileOpenMode, ImageType};
use std::ffi;
use std::ptr;
use std::ops::Range;

pub struct ImageDescription {
    pub data_type: ImageType,
    pub dimensions: Vec<usize>,
}

/// Main entry point to the FITS file format
///
///
pub struct FitsFile {
    pub filename: String,
    fptr: *const sys::fitsfile,
}

impl FitsFile {
    /// Open a fits file from disk
    ///
    /// # Examples
    ///
    /// ```
    /// use fitsio::FitsFile;
    ///
    /// let f = FitsFile::open("../testdata/full_example.fits").unwrap();
    ///
    /// // Continue to use `f` afterwards
    /// ```
    pub fn open<T: Into<String>>(filename: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let filename = filename.into();
        let c_filename = ffi::CString::new(filename.as_str()).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        FileOpenMode::READONLY as libc::c_int,
                        &mut status);
        }

        fits_try!(status,
                  FitsFile {
                      fptr: fptr,
                      filename: filename.clone(),
                  })
    }

    /// Open a fits file in read/write mode
    pub fn edit<T: Into<String>>(filename: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let filename = filename.into();
        let c_filename = ffi::CString::new(filename.as_str()).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut _,
                        c_filename.as_ptr(),
                        FileOpenMode::READWRITE as libc::c_int,
                        &mut status);
        }

        fits_try!(status,
                  FitsFile {
                      fptr: fptr,
                      filename: filename.clone(),
                  })
    }

    /// Create a new fits file on disk
    pub fn create<T: Into<String>>(path: T) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let path = path.into();
        let c_filename = ffi::CString::new(path.as_str()).unwrap();

        unsafe {
            sys::ffinit(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        &mut status);
        }

        fits_try!(status, {
            let f = FitsFile {
                fptr: fptr,
                filename: path.clone(),
            };
            try!(f.add_empty_primary());
            f
        })
    }

    fn add_empty_primary(&self) -> Result<()> {
        let mut status = 0;
        unsafe {
            sys::ffphps(self.fptr as *mut _, 8, 0, ptr::null_mut(), &mut status);
        }

        fits_try!(status, ())
    }

    /// Change the current HDU
    fn change_hdu<T: DescribesHdu>(&self, hdu_description: T) -> Result<()> {
        hdu_description.change_hdu(self)
    }

    /// Return a new HDU object
    pub fn hdu<T: DescribesHdu>(&self, hdu_description: T) -> Result<FitsHdu> {
        FitsHdu::new(self, hdu_description)
    }

    fn hdu_number(&self) -> usize {
        let mut hdu_num = 0;
        unsafe {
            sys::ffghdn(self.fptr as *mut _, &mut hdu_num);
        }
        (hdu_num - 1) as usize
    }

    /// Get the current hdu as an HDU object
    pub fn current_hdu(&self) -> Result<FitsHdu> {
        let current_hdu_number = self.hdu_number();
        self.hdu(current_hdu_number)
    }

    /// Get the current hdu info
    fn fetch_hdu_info(&self) -> Result<HduInfo> {
        let mut status = 0;
        let mut hdu_type = 0;

        unsafe {
            sys::ffghdt(self.fptr as *mut _, &mut hdu_type, &mut status);
        }

        let hdu_type = match hdu_type {
            0 => {
                let mut dimensions = 0;
                unsafe {
                    sys::ffgidm(self.fptr as *mut _, &mut dimensions, &mut status);
                }

                let mut shape = vec![0; dimensions as usize];
                unsafe {
                    sys::ffgisz(self.fptr as *mut _,
                                dimensions,
                                shape.as_mut_ptr(),
                                &mut status);
                }

                HduInfo::ImageInfo { shape: shape.iter().map(|v| *v as usize).collect() }
            }
            1 | 2 => {
                let mut num_rows = 0;
                unsafe {
                    sys::ffgnrw(self.fptr as *mut _, &mut num_rows, &mut status);
                }

                let mut num_cols = 0;
                unsafe {
                    sys::ffgncl(self.fptr as *mut _, &mut num_cols, &mut status);
                }
                let mut column_descriptions = Vec::with_capacity(num_cols as usize);

                for i in 0..num_cols {
                    let mut name_buffer: Vec<libc::c_char> = vec![0; 71];
                    let mut type_buffer: Vec<libc::c_char> = vec![0; 71];
                    unsafe {
                        sys::ffgbcl(self.fptr as *mut _,
                                    (i + 1) as i32,
                                    name_buffer.as_mut_ptr(),
                                    ptr::null_mut(),
                                    type_buffer.as_mut_ptr(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    ptr::null_mut(),
                                    &mut status);
                    }

                    column_descriptions.push(ColumnDescription {
                        name: stringutils::buf_to_string(&name_buffer).unwrap(),
                        data_type: stringutils::buf_to_string(&type_buffer).unwrap(),
                    });
                }

                HduInfo::TableInfo {
                    column_descriptions: column_descriptions,
                    num_rows: num_rows as usize,
                }
            }
            _ => panic!("Invalid hdu type found"),
        };

        fits_try!(status, hdu_type)
    }

    pub fn create_table(&self,
                        extname: String,
                        table_description: &[ColumnDescription])
                        -> Result<FitsHdu> {
        let tfields = {
            let stringlist = table_description.iter()
                .map(|desc| desc.name.clone())
                .collect();
            stringutils::StringList::from_vec(stringlist)
        };

        let ttype = {
            let stringlist = table_description.iter()
                .map(|desc| desc.data_type.clone())
                .collect();
            stringutils::StringList::from_vec(stringlist)
        };

        let c_extname = ffi::CString::new(extname).unwrap();


        let hdu_info = HduInfo::TableInfo {
            column_descriptions: table_description.to_vec(),
            num_rows: 0,
        };

        let mut status: libc::c_int = 0;
        unsafe {
            sys::ffcrtb(self.fptr as *mut _,
                        hdu_info.into(),
                        0,
                        tfields.len as libc::c_int,
                        tfields.list,
                        ttype.list,
                        ptr::null_mut(),
                        c_extname.into_raw(),
                        &mut status);
        }

        if status != 0 {
            Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            })
        } else {
            self.current_hdu()
        }

    }

    pub fn create_image(&self,
                        extname: String,
                        image_description: &ImageDescription)
                        -> Result<FitsHdu> {
        let naxis = image_description.dimensions.len();
        let mut status = 0;
        unsafe {
            sys::ffcrim(self.fptr as *mut _,
                        image_description.data_type.into(),
                        naxis as i32,
                        image_description.dimensions.as_ptr() as *mut i64,
                        &mut status);
        }

        if status != 0 {
            return Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            });
        }

        // Current HDU should be at the new HDU
        let mut current_hdu = try!(self.current_hdu());
        try!(current_hdu.write_key("EXTNAME".into(), extname));

        if status != 0 {
            Err(FitsError {
                status: status,
                message: status_to_string(status).unwrap(),
            })
        } else {
            self.current_hdu()
        }
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        let mut status = 0;
        unsafe {
            sys::ffclos(self.fptr as *mut _, &mut status);
        }
    }
}

/// Hdu description type
///
/// Any way of describing a HDU - number or string which either
/// changes the hdu by absolute number, or by name.
pub trait DescribesHdu {
    fn change_hdu(&self, fptr: &FitsFile) -> Result<()>;
}

impl DescribesHdu for usize {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        unsafe {
            sys::ffmahd(f.fptr as *mut _,
                        (*self + 1) as i32,
                        &mut _hdu_type,
                        &mut status);
        }

        fits_try!(status, ())
    }
}

impl<'a> DescribesHdu for &'a str {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        let c_hdu_name = ffi::CString::new(*self).unwrap();

        unsafe {
            sys::ffmnhd(f.fptr as *mut _,
                        HduInfo::AnyInfo.into(),
                        c_hdu_name.into_raw(),
                        0,
                        &mut status);
        }

        fits_try!(status, ())
    }
}

/// Trait for reading a fits column
pub trait ReadsCol {
    fn read_col<T: Into<String>>(fits_file: &FitsFile, name: T) -> Result<Vec<Self>>
        where Self: Sized;
    fn read_col_range<T: Into<String>>(fits_file: &FitsFile,
                                       name: T,
                                       range: &Range<usize>)
                                       -> Result<Vec<Self>>
        where Self: Sized;
}

macro_rules! reads_col_impl {
    ($t: ty, $func: ident, $nullval: expr) => (
        impl ReadsCol for $t {
            fn read_col<T: Into<String>>(fits_file: &FitsFile, name: T) -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::TableInfo {
                        column_descriptions, num_rows, ..
                    }) => {
                        let mut out = vec![$nullval; num_rows];
                        let test_name = name.into();
                        assert_eq!(out.len(), num_rows);
                        let column_number = column_descriptions.iter().position(|ref desc| {
                            desc.name == test_name
                        }).unwrap();
                        let mut status = 0;
                        unsafe {
                            sys::$func(fits_file.fptr as *mut _,
                                       (column_number + 1) as i32,
                                       1,
                                       1,
                                       num_rows as i64,
                                       $nullval,
                                       out.as_mut_ptr(),
                                       ptr::null_mut(),
                                       &mut status);

                        }
                        fits_try!(status, out)
                    },
                    Err(e) => Err(e),
                    _ => panic!("Unknown error occurred"),
                }
            }

            // TODO: should we check the bounds? cfitsio will raise an error, but we
            // could be more friendly and raise our own?
            fn read_col_range<T: Into<String>>(fits_file: &FitsFile, name: T, range: &Range<usize>)
                -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::TableInfo { column_descriptions, .. }) => {
                        let num_output_rows = range.end - range.start + 1;
                        let mut out = vec![$nullval; num_output_rows];
                        let test_name = name.into();
                        let column_number = column_descriptions.iter().position(|ref desc| {
                            desc.name == test_name
                        }).unwrap();
                        let mut status = 0;
                        unsafe {
                            sys::$func(fits_file.fptr as *mut _,
                                       (column_number + 1) as i32,
                                       (range.start + 1) as i64,
                                       1,
                                       num_output_rows as _,
                                       $nullval,
                                       out.as_mut_ptr(),
                                       ptr::null_mut(),
                                       &mut status);

                        }
                        fits_try!(status, out)
                    },
                    Err(e) => Err(e),
                    _ => panic!("Unknown error occurred"),
                }
            }
        }
    )
}

reads_col_impl!(i32, ffgcvk, 0);
reads_col_impl!(u32, ffgcvuk, 0);
reads_col_impl!(i64, ffgcvj, 0);
reads_col_impl!(u64, ffgcvuj, 0);
reads_col_impl!(f32, ffgcve, 0.0);
reads_col_impl!(f64, ffgcvd, 0.0);

// TODO: impl for string

pub trait WritesCol {
    fn write_col<T: Into<String>>(fits_file: &FitsFile,
                                  hdu: &FitsHdu,
                                  col_name: T,
                                  col_data: &[Self])
                                  -> Result<()>
        where Self: Sized;
    fn write_col_range<T: Into<String>>(fits_file: &FitsFile,
                                        hdu: &FitsHdu,
                                        col_name: T,
                                        col_data: &[Self],
                                        rows: &Range<usize>)
                                        -> Result<()>
        where Self: Sized;
}

macro_rules! writes_col_impl {
    ($t: ty, $data_type: expr) => (
        impl WritesCol for $t {
            fn write_col<T: Into<String>>(
                fits_file: &FitsFile,
                hdu: &FitsHdu,
                col_name: T,
                col_data: &[Self]) -> Result<()> {
                let colno = hdu.get_column_no(col_name.into())?;
                let mut status = 0;
                unsafe {
                    sys::ffpcl(
                        fits_file.fptr as *mut _,
                        $data_type.into(),
                        (colno + 1) as _,
                        1,
                        1,
                        col_data.len() as _,
                        col_data.as_ptr() as *mut _,
                        &mut status);
                }
                fits_try!(status, ())
            }

            fn write_col_range<T: Into<String>>(fits_file: &FitsFile,
                hdu: &FitsHdu,
                col_name: T,
                col_data: &[Self],
                rows: &Range<usize>)
            -> Result<()> {
                let colno = hdu.get_column_no(col_name.into())?;
                let mut status = 0;
                unsafe {
                    sys::ffpcl(
                        fits_file.fptr as *mut _,
                        $data_type.into(),
                        (colno + 1) as _,
                        (rows.start + 1) as _,
                        1,
                        (rows.end + 1) as _,
                        col_data.as_ptr() as *mut _,
                        &mut status
                    );
                }
                fits_try!(status, ())
            }
        }
    )
}

writes_col_impl!(u32, DataType::TUINT);
writes_col_impl!(u64, DataType::TULONG);
writes_col_impl!(i32, DataType::TINT);
writes_col_impl!(i64, DataType::TLONG);
writes_col_impl!(f32, DataType::TFLOAT);
writes_col_impl!(f64, DataType::TDOUBLE);

/// Trait applied to types which can be read from a FITS header
///
/// This is currently:
///
/// * i32
/// * i64
/// * f32
/// * f64
/// * String
pub trait ReadsKey {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> where Self: Sized;
}

macro_rules! reads_key_impl {
    ($t:ty, $func:ident) => (
        impl ReadsKey for $t {
            fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;
                let mut value: Self = Self::default();

                unsafe {
                    sys::$func(f.fptr as *mut _,
                           c_name.into_raw(),
                           &mut value,
                           ptr::null_mut(),
                           &mut status);
                }

                fits_try!(status, value)
            }
        }
    )
}

reads_key_impl!(i32, ffgkyl);
reads_key_impl!(i64, ffgkyj);
reads_key_impl!(f32, ffgkye);
reads_key_impl!(f64, ffgkyd);

impl ReadsKey for String {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;
        let mut value: Vec<libc::c_char> = vec![0; sys::MAX_VALUE_LENGTH];

        unsafe {
            sys::ffgkys(f.fptr as *mut _,
                        c_name.into_raw(),
                        value.as_mut_ptr(),
                        ptr::null_mut(),
                        &mut status);
        }

        fits_try!(status, {
            let value: Vec<u8> = value.iter()
                .map(|&x| x as u8)
                .filter(|&x| x != 0)
                .collect();
            String::from_utf8(value).unwrap()
        })
    }
}

/// Writing a fits keyword
pub trait WritesKey {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()>;
}

macro_rules! writes_key_impl_flt {
    ($t:ty, $func:ident) => (
        impl WritesKey for $t {
            fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;

                unsafe {
                    sys::$func(f.fptr as *mut _,
                                c_name.into_raw(),
                                value,
                                9,
                                ptr::null_mut(),
                                &mut status);
                }
                fits_try!(status, ())
            }
        }
    )
}

impl WritesKey for i64 {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;

        unsafe {
            sys::ffpkyj(f.fptr as *mut _,
                        c_name.into_raw(),
                        value,
                        ptr::null_mut(),
                        &mut status);
        }
        fits_try!(status, ())
    }
}

writes_key_impl_flt!(f32, ffpkye);
writes_key_impl_flt!(f64, ffpkyd);

impl WritesKey for String {
    fn write_key(f: &FitsFile, name: &str, value: Self) -> Result<()> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;

        unsafe {
            sys::ffpkys(f.fptr as *mut _,
                        c_name.into_raw(),
                        ffi::CString::new(value).unwrap().into_raw(),
                        ptr::null_mut(),
                        &mut status);
        }

        fits_try!(status, ())
    }
}

/// Reading fits images
pub trait ReadWriteImage: Sized {
    /// Read pixels from an image between a start index and end index
    ///
    /// Start and end are read inclusively, so start = 0, end = 10 will read 11 pixels
    /// in a row.
    fn read_section(fits_file: &FitsFile, start: usize, end: usize) -> Result<Vec<Self>>;

    /// Read a row of pixels from a fits image
    fn read_rows(fits_file: &FitsFile, start_row: usize, num_rows: usize) -> Result<Vec<Self>>;

    /// Read a single row from the image HDU
    fn read_row(fits_file: &FitsFile, row: usize) -> Result<Vec<Self>>;

    /// Read a square region from the chip.
    ///
    /// Lower left indicates the starting point of the square, and the upper
    /// right defines the pixel _beyond_ the end. The range of pixels included
    /// is inclusive of the lower end, and *exclusive* of the upper end.
    fn read_region(fits_file: &FitsFile, ranges: &[&Range<usize>]) -> Result<Vec<Self>>;

    /// Read a whole image into a new `Vec`
    ///
    /// This reads an entire image into a one-dimensional vector
    fn read_image(fits_file: &FitsFile) -> Result<Vec<Self>> {
        match fits_file.fetch_hdu_info() {
            Ok(HduInfo::ImageInfo { shape }) => {
                let mut npixels = 1;
                for dimension in &shape {
                    npixels *= *dimension;
                }
                Self::read_section(fits_file, 0, npixels)
            }
            Ok(HduInfo::TableInfo { .. }) => {
                Err(FitsError {
                    status: 601,
                    message: "cannot read image data from a table hdu".to_string(),
                })
            }
            Ok(HduInfo::AnyInfo) => unreachable!(),
            Err(e) => Err(e),
        }
    }

    fn write_section(fits_file: &FitsFile, start: usize, end: usize, data: &[Self]) -> Result<()>;

    fn write_region(fits_file: &FitsFile, ranges: &[&Range<usize>], data: &[Self]) -> Result<()>;
}

macro_rules! read_write_image_impl {
    ($t: ty, $data_type: expr) => (
        impl ReadWriteImage for $t {
            fn read_section(fits_file: &FitsFile, start: usize, end: usize) -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::ImageInfo { shape: _shape }) => {
                        let nelements = end - start;
                        let mut out = vec![0 as $t; nelements];
                        let mut status = 0;

                        unsafe {
                            sys::ffgpv(fits_file.fptr as *mut _,
                                       $data_type.into(),
                                       (start + 1) as i64,
                                       nelements as i64,
                                       ptr::null_mut(),
                                       out.as_mut_ptr() as *mut libc::c_void,
                                       ptr::null_mut(),
                                       &mut status);
                        }

                        fits_try!(status, out)

                    },
                    Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                        status: 601,
                        message: "cannot read image data from a table hdu".to_string(),
                    }),
                    Ok(HduInfo::AnyInfo) => unreachable!(),
                    Err(e) => Err(e),
                }
            }

            fn read_rows(fits_file: &FitsFile, start_row: usize, num_rows: usize)
                -> Result<Vec<Self>> {
                match fits_file.fetch_hdu_info() {
                    Ok(HduInfo::ImageInfo { shape }) => {
                        if shape.len() != 2 {
                            unimplemented!();
                        }

                        let num_cols = shape[1];
                        let start = start_row * num_cols;
                        let end = (start_row + num_rows) * num_cols;

                        Self::read_section(fits_file, start, end)
                    },
                    Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                        status: 601,
                        message: "cannot read image data from a table hdu".to_string(),
                    }),
                    Ok(HduInfo::AnyInfo) => unreachable!(),
                    Err(e) => Err(e),
                }
            }

            fn read_row(fits_file: &FitsFile, row: usize) -> Result<Vec<Self>> {
                Self::read_rows(fits_file, row, 1)
            }

            fn read_region(fits_file: &FitsFile, ranges: &[&Range<usize>])
                -> Result<Vec<Self>> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { shape }) => {
                            if shape.len() != 2 {
                                unimplemented!();
                            }

                            if ranges.len() != 2 {
                                unimplemented!();
                            }

                            // These have to be mutable because of the C-api
                            let mut fpixel = [
                                (ranges[0].start + 1) as _,
                                (ranges[1].start + 1) as _
                            ];
                            let mut lpixel = [
                                (ranges[0].end + 1) as _,
                                (ranges[1].end + 1) as _
                            ];

                            let mut inc = [ 1, 1 ];
                            let nelements =
                                ((lpixel[0] - fpixel[0]) + 1) * ((lpixel[1] - fpixel[1]) + 1);
                            let mut out = vec![0 as $t; nelements as usize];
                            let mut status = 0;

                            unsafe {
                                sys::ffgsv(
                                    fits_file.fptr as *mut _,
                                    $data_type.into(),
                                    fpixel.as_mut_ptr(),
                                    lpixel.as_mut_ptr(),
                                    inc.as_mut_ptr(),
                                    ptr::null_mut(),
                                    out.as_mut_ptr() as *mut libc::c_void,
                                    ptr::null_mut(),
                                    &mut status);

                            }

                            fits_try!(status, out)
                        }
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot read image data from a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }

            fn write_section(
                fits_file: &FitsFile,
                start: usize,
                end: usize,
                data: &[Self])
                -> Result<()> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { .. }) => {
                            let nelements = end - start;
                            assert!(data.len() >= nelements);
                            let mut status = 0;
                            unsafe {
                                sys::ffppr(fits_file.fptr as *mut _,
                                           $data_type.into(),
                                           (start + 1) as i64,
                                           nelements as i64,
                                           data.as_ptr() as *mut _,
                                           &mut status);
                            }

                            fits_try!(status, ())
                        },
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot write image data to a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }

            fn write_region(
                fits_file: &FitsFile,
                ranges: &[&Range<usize>],
                data: &[Self])
                -> Result<()> {
                    match fits_file.fetch_hdu_info() {
                        Ok(HduInfo::ImageInfo { .. }) => {
                            let mut fpixel = [
                                (ranges[0].start + 1) as _,
                                (ranges[1].start + 1) as _
                            ];
                            let mut lpixel = [
                                (ranges[1].end + 1) as _,
                                (ranges[1].end + 1) as _
                            ];
                            let mut status = 0;

                            unsafe {
                                sys::ffpss(
                                    fits_file.fptr as *mut _,
                                    $data_type.into(),
                                    fpixel.as_mut_ptr(),
                                    lpixel.as_mut_ptr(),
                                    data.as_ptr() as *mut libc::c_void,
                                    &mut status);
                            }

                            fits_try!(status, ())
                        },
                        Ok(HduInfo::TableInfo { .. }) => Err(FitsError {
                            status: 601,
                            message: "cannot write image data to a table hdu".to_string(),
                        }),
                        Ok(HduInfo::AnyInfo) => unreachable!(),
                        Err(e) => Err(e),
                    }
                }
        }
    )
}


read_write_image_impl!(i8, DataType::TSHORT);
read_write_image_impl!(i32, DataType::TINT);
read_write_image_impl!(i64, DataType::TLONG);
read_write_image_impl!(u8, DataType::TUSHORT);
read_write_image_impl!(u32, DataType::TUINT);
read_write_image_impl!(u64, DataType::TULONG);
read_write_image_impl!(f32, DataType::TFLOAT);
read_write_image_impl!(f64, DataType::TDOUBLE);

pub enum Column {
    Int32 { name: String, data: Vec<i32> },
    Int64 { name: String, data: Vec<i64> },
    Float { name: String, data: Vec<f32> },
    Double { name: String, data: Vec<f64> },
}

pub struct ColumnIterator<'a> {
    current: usize,
    column_descriptions: Vec<ColumnDescription>,
    fits_file: &'a FitsFile,
}

impl<'a> ColumnIterator<'a> {
    fn new(fits_file: &'a FitsFile) -> Self {
        match fits_file.fetch_hdu_info() {
            Ok(HduInfo::TableInfo { column_descriptions, num_rows: _num_rows }) => {
                ColumnIterator {
                    current: 0,
                    column_descriptions: column_descriptions,
                    fits_file: fits_file,
                }
            }
            Err(e) => panic!("{:?}", e),
            _ => panic!("Unknown error occurred"),
        }
    }
}

impl<'a> Iterator for ColumnIterator<'a> {
    type Item = Column;

    fn next(&mut self) -> Option<Self::Item> {
        let ncols = self.column_descriptions.len();

        if self.current < ncols {
            let description = &self.column_descriptions[self.current];
            let current_name = description.name.as_str();
            let current_type = typechar_to_data_type(description.data_type.as_str());

            let retval = match current_type {
                DataType::TSHORT => {
                    i32::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Int32 {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TLONG => {
                    i64::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Int64 {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TFLOAT => {
                    f32::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Float {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                DataType::TDOUBLE => {
                    f64::read_col(self.fits_file, current_name)
                        .map(|data| {
                            Some(Column::Double {
                                name: current_name.to_string(),
                                data: data,
                            })
                        })
                        .unwrap()
                }
                _ => unimplemented!(),
            };

            self.current += 1;

            retval

        } else {
            None
        }
    }
}

pub struct FitsHdu<'open> {
    fits_file: &'open FitsFile,
    pub info: HduInfo,
}

impl<'open> FitsHdu<'open> {
    pub fn new<T: DescribesHdu>(fits_file: &'open FitsFile, hdu_description: T) -> Result<Self> {
        try!(fits_file.change_hdu(hdu_description));
        match fits_file.fetch_hdu_info() {
            Ok(hdu_info) => {
                Ok(FitsHdu {
                    fits_file: fits_file,
                    info: hdu_info,
                })
            }
            Err(e) => Err(e),
        }
    }

    /// Read header key
    pub fn read_key<T: ReadsKey>(&self, name: &str) -> Result<T> {
        T::read_key(self.fits_file, name)
    }

    /// Write header key
    pub fn write_key<T: WritesKey>(&mut self, name: &str, value: T) -> Result<()> {
        T::write_key(self.fits_file, name, value)
    }

    /// Read an image between pixel a and pixel b into a `Vec`
    pub fn read_section<T: ReadWriteImage>(&self, start: usize, end: usize) -> Result<Vec<T>> {
        T::read_section(self.fits_file, start, end)
    }

    /// Read multiple rows from a fits image
    pub fn read_rows<T: ReadWriteImage>(&self,
                                        start_row: usize,
                                        num_rows: usize)
                                        -> Result<Vec<T>> {
        T::read_rows(self.fits_file, start_row, num_rows)
    }

    /// Read a single row from a fits image
    pub fn read_row<T: ReadWriteImage>(&self, row: usize) -> Result<Vec<T>> {
        T::read_row(self.fits_file, row)
    }

    /// Read a whole fits image into a vector
    pub fn read_image<T: ReadWriteImage>(&self) -> Result<Vec<T>> {
        T::read_image(self.fits_file)
    }

    /// Write contiguous data to a fits image
    pub fn write_section<T: ReadWriteImage>(&mut self,
                                            start: usize,
                                            end: usize,
                                            data: &[T])
                                            -> Result<()> {
        T::write_section(self.fits_file, start, end, data)
    }

    /// Write a rectangular region to a fits image
    pub fn write_region<T: ReadWriteImage>(&mut self,
                                           ranges: &[&Range<usize>],
                                           data: &[T])
                                           -> Result<()> {
        T::write_region(self.fits_file, ranges, data)
    }

    /// Read a square region into a `Vec`
    pub fn read_region<T: ReadWriteImage>(&self, ranges: &[&Range<usize>]) -> Result<Vec<T>> {
        T::read_region(self.fits_file, ranges)
    }

    pub fn get_column_no<T: Into<String>>(&self, col_name: T) -> Result<usize> {
        let mut status = 0;
        let mut colno = 0;

        let c_col_name = {
            let col_name = col_name.into();
            ffi::CString::new(col_name.as_str()).unwrap()
        };

        unsafe {
            sys::ffgcno(self.fits_file.fptr as *mut _,
                        CaseSensitivity::CASEINSEN as _,
                        c_col_name.as_ptr() as *mut _,
                        &mut colno,
                        &mut status);
        }
        fits_try!(status, (colno - 1) as usize)
    }

    /// Read a binary table column
    pub fn read_col<T: ReadsCol>(&self, name: &str) -> Result<Vec<T>> {
        T::read_col(self.fits_file, name)
    }

    pub fn read_col_range<T: ReadsCol>(&self, name: &str, range: &Range<usize>) -> Result<Vec<T>> {
        T::read_col_range(self.fits_file, name, range)
    }

    pub fn write_col<T: WritesCol, N: Into<String>>(&mut self,
                                                    name: N,
                                                    col_data: &[T])
                                                    -> Result<()> {
        T::write_col(self.fits_file, self, name, col_data)
    }

    pub fn write_col_range<T: WritesCol, N: Into<String>>(&mut self,
                                                          name: N,
                                                          col_data: &[T],
                                                          rows: &Range<usize>)
                                                          -> Result<()> {
        T::write_col_range(self.fits_file, self, name, col_data, rows)
    }

    pub fn columns(&self) -> ColumnIterator {
        ColumnIterator::new(self.fits_file)
    }
}


#[cfg(test)]
mod test {
    extern crate tempdir;

    use super::FitsHdu;
    use super::super::fitsfile::FitsFile;
    use super::super::types::HduInfo;
    use super::super::types::*;
    use super::ImageDescription;
    use fitserror::FitsError;
    use conversions::typechar_to_data_type;
    use std::{f32, f64};

    // FitsFile tests
    #[test]
    fn typechar_conversions() {
        let input = vec!["X", "B", "L", "A", "I", "J", "E", "D", "C", "M"];
        let expected = vec![DataType::TBIT,
                            DataType::TBYTE,
                            DataType::TLOGICAL,
                            DataType::TSTRING,
                            DataType::TSHORT,
                            DataType::TLONG,
                            DataType::TFLOAT,
                            DataType::TDOUBLE,
                            DataType::TCOMPLEX,
                            DataType::TDBLCOMPLEX];

        for (i, e) in input.iter().zip(expected) {
            assert_eq!(typechar_to_data_type(i), e);
        }
    }

    #[test]
    fn opening_an_existing_file() {
        match FitsFile::open("../testdata/full_example.fits") {
            Ok(_) => {}
            Err(e) => panic!("{:?}", e),
        }
    }

    #[test]
    fn creating_a_new_file() {
        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");
        assert!(!filename.exists());

        FitsFile::create(filename.to_str().unwrap())
            .map(|f| {
                assert!(filename.exists());

                // Ensure the empty primary has been written
                let naxis: i64 = f.hdu(0)
                    .unwrap()
                    .read_key("NAXIS")
                    .unwrap();
                assert_eq!(naxis, 0);
            })
            .unwrap();
    }

    #[test]
    fn editing_a_current_file() {
        use std::fs;

        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");

        fs::copy("../testdata/full_example.fits", &filename).expect("Could not copy test file");

        {
            let f = FitsFile::edit(filename.to_str().unwrap()).unwrap();
            let mut image_hdu = f.hdu(0).unwrap();

            let data_to_write: Vec<i64> = (0..100).map(|_| 10101).collect();
            image_hdu.write_section(0, 100, &data_to_write).unwrap();
        }

        {
            let f = FitsFile::open(filename.to_str().unwrap()).unwrap();
            let hdu = f.hdu(0).unwrap();
            let read_data: Vec<i64> = hdu.read_section(0, 10).unwrap();
            assert_eq!(read_data, vec![10101; 10]);
        }
    }

    #[test]
    fn fetching_a_hdu() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        for i in 0..2 {
            f.change_hdu(i).unwrap();
            assert_eq!(f.hdu_number(), i);
        }

        match f.change_hdu(2) {
            Err(e) => assert_eq!(e.status, 107),
            _ => panic!("Error checking for failure"),
        }

        f.change_hdu("TESTEXT").unwrap();
        assert_eq!(f.hdu_number(), 1);
    }

    #[test]
    fn fetching_hdu_info() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        match f.fetch_hdu_info() {
            Ok(HduInfo::ImageInfo { shape }) => {
                assert_eq!(shape.len(), 2);
                assert_eq!(shape, vec![100, 100]);
            }
            Err(e) => panic!("Error fetching hdu info {:?}", e),
            _ => panic!("Unknown error"),
        }

        f.change_hdu(1).unwrap();
        match f.fetch_hdu_info() {
            Ok(HduInfo::TableInfo { column_descriptions, num_rows }) => {
                assert_eq!(num_rows, 50);
                assert_eq!(column_descriptions.iter()
                               .map(|desc| desc.name.clone())
                               .collect::<Vec<String>>(),
                           vec!["intcol".to_string(),
                                "floatcol".to_string(),
                                "doublecol".to_string()]);
                assert_eq!(column_descriptions.iter()
                               .map(|ref desc| typechar_to_data_type(desc.data_type.clone()))
                               .collect::<Vec<DataType>>(),
                           vec![DataType::TLONG, DataType::TFLOAT, DataType::TDOUBLE]);
            }
            Err(e) => panic!("Error fetching hdu info {:?}", e),
            _ => panic!("Unknown error"),
        }
    }

    #[test]
    fn test_fits_try() {
        use stringutils;

        let status = 0;
        assert_eq!(fits_try!(status, 10), Ok(10));

        let status = 105;
        assert_eq!(fits_try!(status, 10),
                   Err(FitsError {
                       status: status,
                       message: stringutils::status_to_string(status).unwrap(),
                   }));
    }
{'fraction_non_alphanumeric': 0.1173020189914262, 'fraction_numerical': 0.0073061676039457915, 'mean_word_length': 1.1221540192218336, 'pattern_counts': {'":': 0, '<': 317, '<?xml version=': 0, '>': 554, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 250, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '78782', 'n_tokens_mistral': 24966, 'n_tokens_neox': 23511, 'n_words': 5040}