use std::{error::Error, fs::File, io::Write, path::Path};
use clap::Parser;
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::{print_good, print_error, print_info}, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_ecs}}};
use super::settings::{Args, Tools, TileOutputFormat, TileOrder};

pub fn run_command() -> Result<(), Box<dyn Error>> {
    let cli = Args::parse();

    match cli.tool {
        Tools::Tileset { input_file, output_file, format, tile_order } =>
            tileset::generate(
                &input_file,
                &output_file,
                match format {
                    TileOutputFormat::Bin => "bin",
                    TileOutputFormat::Inc => "inc"
                },
                match tile_order {
                    TileOrder::Tile => "tile",
                    TileOrder::Sprite => "sprite"
                }
            )?,
        Tools::Soundtrack { input_files, output_directory, input_format: _, output_format: _, source_file_format: _, source_file_output_directory, artifact_output_directory } => {
            if input_files.is_empty() {
                return Err( "no input files (see `reskit soundtrack --help` for more info)" )?;
            }

            let output_directory = if output_directory.ends_with( "/" ) { output_directory.to_string() } else { format!( "{}/", output_directory ) };
            let artifact_output_directory = if artifact_output_directory.ends_with( "/" ) { artifact_output_directory.to_string() } else { format!( "{}/", artifact_output_directory ) };
            let source_file_output_directory = if source_file_output_directory.ends_with( "/" ) { source_file_output_directory.to_string() } else { format!( "{}/", source_file_output_directory ) };

            let modules: Vec<DmfModule> = input_files.iter().map( | filename | Ok( DmfModule::from_file( &filename )? ) ).collect::<Result<Vec<DmfModule>, Box<dyn Error>>>()?;

            let mut combined_asset_list = DmfModule::get_combined_assets_list( artifact_output_directory.clone(), &modules )?;
            if combined_asset_list.instruments.len() > 255 {
                return Err( "combined instrument set exceeds 255 instruments; try converting fewer modules or modules with fewer instruments" )?;
            }
            if combined_asset_list.samples.len() > 255 {
                return Err( "combined sample set exceeds 255 samples; try converting fewer modules or modules with fewer samples" )?;
            }

            // Output the ESF sequences
            for i in 0..modules.len() {
                let module = &modules[ i ];

                let path = Path::new( &module.path );
                let original_filename_stem = path.file_stem().ok_or( "internal error: unable to parse filename" )?.to_string_lossy();
                let output_file = format!( "{}{}.esf", output_directory, original_filename_stem );

                combined_asset_list.sequences.push( output_file.clone() );

                println!( "Creating ESF output file {}", output_file );
                let mut file = File::create( &output_file )?;
                file.write_all( &module.get_esf( &combined_asset_list )? )?;

                print_good( &format!( "successfully compiled soundtrack for {}", output_file ) );
            }

            // Output the shared artifacts used by all ESF sequences
            println!( "Writing sequence artifacts..." );
            let shared_artifacts = combined_asset_list.to_bytes()?;
            println!( "Writing sequence artifact {}", format!( "{}music.asm", source_file_output_directory ) );
            let mut file = File::create( format!( "{}music.asm", source_file_output_directory ) )?;
            file.write_all( &shared_artifacts )?;

            print_good( "all files converted successfully" );
        }
        Tools::Level { input_file, output_directory, console: _ } => {
            let tiled_file = get_tiled_tilemap( &input_file )?;

            // Get tile and palette files
            let ( all_tiles, palettes ) = get_tiles( &tiled_file )?;

            let mut tiles_bin = File::create( format!( "{}tiles.bin", output_directory ) )?;
            tiles_bin.write_all( &all_tiles )?;
            print_good( "exported tiles.bin" );

            let mut palettes_bin = File::create( format!( "{}palettes.pal", output_directory ) )?;
            palettes_bin.write_all( &palettes )?;
            print_good( "exported palettes.pal" );

            let mut nametables_bin = File::create( format!( "{}nametables.map", output_directory ) )?;
            nametables_bin.write_all( &get_tilemap( &tiled_file )? )?;
            print_good( "exported nametables.map" );

            let mut nametables_bin = File::create( format!( "{}collision.lvc", output_directory ) )?;
            nametables_bin.write_all( &get_collision_map( &tiled_file )? )?;
            print_good( "exported collision.lvc" );

            let mut nametables_bin = File::create( format!( "{}objects.ecs", output_directory ) )?;
            nametables_bin.write_all( &get_ecs( &tiled_file )? )?;
            print_good( "exported objects.ecs" );

            let mut code_asm = File::create( format!( "{}level.asm", output_directory ) )?;
            code_asm.write_all( &get_code( &tiled_file, "testlevel", "levels/" )?.as_bytes() )?;
            print_good( "exported level.asm" );
        }
    };

    Ok( () )
}