use std::{slice::Iter, error::Error, str::from_utf8, convert::TryInto};

use convert_case::{Casing, Case};

pub struct Ring< Type: Copy >{
    data: Vec< Type >,
    position: usize
}

impl< Type: Copy > Ring< Type > {

    pub fn create( data: Vec< Type >, start_position: Option<usize> ) -> Ring< Type > {
        Ring {
            data,
            position: if let Some( position ) = start_position {
                position
            } else {
                0
            }
        }
    }

    pub fn len( &self ) -> usize {
        self.data.len()
    }

}

impl< Type: Copy > Iterator for Ring< Type > {
    type Item = Type;

    fn next( &mut self ) -> Option< Self::Item > {
        let item = self.data[ self.position % self.data.len() ];

        self.position += 1;

        Some( item )
    }

}

/**
 * Protect against directory traversal attacks when using identifiers inside of files
 */
pub fn sanitize_string( string: String ) -> String {
    string
        .replace( "../", "_" )
        .replace( "..\\", "_" )
}

pub fn symbol_to_pascal( filename: &str ) -> String {
    filename
        .replace( "/", "_" )
        .replace( "\\", "_" )
        .replace( ".", "_" )
        .replace( "-", "_" )
        .to_case( Case::Pascal )
}

pub fn print_error( error_msg: &str ) {
	red!( "fatal: " ); println!( "{}", error_msg );
}

pub fn print_good( message: &str ) {
	green!( "success: " ); println!( "{}", message );
}

pub fn print_warning( message: &str ) {
	yellow!( "warning: " ); println!( "{}", message );
}

pub fn print_info( message: &str ) {
    cyan!( "info: " ); println!( "{}", message );
}

pub fn get_string( bytes: &mut Iter<'_, u8>, take: usize ) -> Result<String, Box<dyn Error>> {
    let took = bytes.take( take );
    let took: Vec<u8> = took.cloned().collect();
    let took = from_utf8( &took )?.to_string();

    Ok( took )
}

pub fn get_u8( bytes: &mut Iter<'_, u8> ) -> Result<u8, Box<dyn Error>> {
    let took = bytes.take( 1 ).cloned().next().ok_or( "invalid file: expected 1 byte" )?;

    Ok( took )
}

pub fn get_i8( bytes: &mut Iter<'_, u8> ) -> Result<i8, Box<dyn Error>> {
    let took = bytes.take( 1 ).cloned().next().ok_or( "invalid file: expected 1 byte" )?;

    Ok( took as i8 )
}

pub fn get_u16( bytes: &mut Iter<'_, u8> ) -> Result<u16, Box<dyn Error>> {
    let took = bytes.take( 2 );
    let took: Vec<u8> = took.cloned().collect();
    let took = u16::from_le_bytes( took[0..2].try_into()? );

    Ok( took )
}

pub fn get_i16( bytes: &mut Iter<'_, u8> ) -> Result<i16, Box<dyn Error>> {
    let took = bytes.take( 2 );
    let took: Vec<u8> = took.cloned().collect();
    let took = i16::from_le_bytes( took[0..2].try_into()? );

    Ok( took )
}

pub fn get_u32( bytes: &mut Iter<'_, u8> ) -> Result<u32, Box<dyn Error>> {
    let took = bytes.take( 4 );
    let took: Vec<u8> = took.cloned().collect();
    let took = u32::from_le_bytes( took[0..4].try_into()? );

    Ok( took )
}

pub fn get_i32( bytes: &mut Iter<'_, u8> ) -> Result<i32, Box<dyn Error>> {
    let took = bytes.take( 4 );
    let took: Vec<u8> = took.cloned().collect();
    let took = i32::from_le_bytes( took[0..4].try_into()? );

    Ok( took )
}

pub fn skip( bytes: &mut Iter<'_, u8>, by: usize ) {
    for _i in 0..by {
        bytes.next();
    }
}