extern crate alto;

use std::env;
use std::process::exit;
use alto::{Alto, AltoError, AltoResult, Context, Mono, Source};//, StaticSource, Stereo, 

fn main() {
    let args: Vec<String> = env::args().collect();

    let height = if args.len() >= 2 { (&args[1]).parse::<u32>().unwrap()} else { 8 as u32 };

	let context = &configured_context().unwrap();

//	//test
//    if let Err(e) = play(context, 220.0, 1000) {
//        println!("Failed to run basic example: {}", e);
//        exit(1);
//    }
//    if let Err(e) = play(context, 440.0, 1000) {
//        println!("Failed to run basic example: {}", e);
//        exit(1);
//    }

	let player = PlayTowerOfHanoi{context:context};

//	player.move_discs(3, 1, 3, 1, 3);

	player.move_discs(height as u32, 1, 3, 1, height as u32);
}

struct PlayTowerOfHanoi<'c> {
	context: &'c Context
}

trait PlayTowerOfHanoiTrait {
    fn move_discs(&self, height:u32, from:u8 , to:u8, top:u32, buttom:u32 );

    fn get_third(&self, from:u8 , to:u8 ) -> u8;

    fn get_movement(&self, from:u8 , to:u8 ) -> i8;

	fn play(&self, frequency:f32, duration:u32); // -> AltoResult<()>
}


impl PlayTowerOfHanoiTrait for PlayTowerOfHanoi<'_> {
	fn move_discs(&self, height:u32, from:u8 , to:u8, top:u32, buttom:u32 ){
		if height == 1 {
			println!("Moving disc size {}/{} from {} to {}", top, buttom, from, to);
			let mv = self.get_movement(from, to);
			
			let d = 75.0f32;
			let dur = (d*(top as f32).sqrt()) as u32;
			
			if mv == 1 {
				self.play(_C4, dur);
				self.play(_D4, dur);
			} else if mv == 2 {
				self.play(_F4, dur);
				self.play(_E4, dur);
			} else if mv == 3 {
				self.play(_G4, dur);
				self.play(_A4, dur);
			} else if mv == -1 {
				self.play(_D4, dur);
				self.play(_C4, dur);
			} else if mv == -2 {
				self.play(_E4, dur);
				self.play(_F4, dur);
			} else if mv == -3 {
				self.play(_A4, dur);
				self.play(_G4, dur);
			}
		} else {
			let third:u8 = self.get_third(from, to);
			self.move_discs(height - 1, from, third, top, buttom-1) ;
			self.move_discs(1, from, to, buttom, buttom) ;
			self.move_discs(height - 1, third, to, top, buttom-1) ;
		}
	}

	fn get_third(&self, from:u8 , to:u8 ) -> u8 {
		if from == 1 && to == 2 {
			return 3;
		} else if from == 3 && to == 2 {
			return 1;
		} else if from == 1 && to == 3 {
			return 2;
		} else if to == 1 && from == 2 {
			return 3;
		} else if to == 3 && from == 2 {
			return 1;
		} else if to == 1 && from == 3 {
			return 2;
		} else {
			return 0;
		}
	}

	fn get_movement(&self, from:u8 , to:u8 ) -> i8 {
		if from == 1 && to == 2 {
			return 3;
		} else if from == 3 && to == 2 {
			return 1;
		} else if from == 1 && to == 3 {
			return 2;
		} else if to == 1 && from == 2 {
			return -3;
		} else if to == 3 && from == 2 {
			return -1;
		} else if to == 1 && from == 3 {
			return -2;
		} else {
			return 0;
		}
	}

	fn play(&self, frequency:f32, duration:u32) {
		if let Err(e) = play(self.context, frequency, duration) {
			println!("Failed to play sound: {}", e);
			exit(2);
		}
	}
}

fn configured_context() -> Result<Context, AltoError> {
//fn configured_context() -> AltoResult<()> 
    let alto = Alto::load_default().unwrap();

    for s in alto.enumerate_outputs() {
        println!("Found device: {}", s.to_str().unwrap());
    }

    let device = alto.open(None).unwrap(); // Opens the default audio device
    let context = device.new_context(None)?; // Creates a default context

    // Configure listener
    context.set_position([1.0, 4.0, 5.0]).unwrap();
    context.set_velocity([2.5, 0.0, 0.0]).unwrap();
    context.set_orientation(([0.0, 0.0, 1.0], [0.0, 1.0, 0.0])).unwrap();

	return Ok(context);
}
    
fn play(context:&Context, frequency:f32, duration:u32) -> AltoResult<()> {
	use std::f32::consts::PI;
	use std::sync::Arc;
	use std::{thread, time};

    let mut _source = context.new_static_source()?;
    
    let sampling = (duration * 44100) / 1000;
//    let data: Vec<_> = (0..88200u32)
    let data: Vec<_> = (0..sampling)
//waveform exponential decaying sinus
//        .map(|i| ((i16::MAX as f32) * f32::sin(2.0 * PI * (i as f32) * (1.0f32 + 1.0f32/44100.0f32).powf(-(i as f32)) * frequency / 44100.0)) as i16)
//waveform sinus
        .map(|i| ((i16::MAX as f32) * f32::sin(2.0 * PI * (i as f32) * frequency / 44100.0)) as i16)
        .collect();	
        
    let buffer = context.new_buffer::<Mono<i16>, _>(data, 44_100);
	let buf = Arc::new(buffer.unwrap());
	
	let good_result = _source.set_buffer(buf);
	assert!(good_result.is_ok() && !good_result.is_err());

	_source.play(); 
    thread::sleep(time::Duration::from_millis(duration as u64));

    Ok(())
}

const _C4:f32 = 261.63;
const _DB4:f32 =	277.18;
const _D4:f32 = 293.66;
const _EB4:f32 =	311.13;
const _E4:f32 = 329.63;
const _F4:f32 = 349.23;
const _GB4:f32 =	369.99;
const _G4:f32 = 392.00;
const _AB4:f32 =	415.30;
const _A4:f32 = 440.00;
const _BB4:f32 =	466.16;
const _B4:f32 = 493.88;
