var bricks = window.bricks || {};

bricks.MediaRecorder = class extends bricks.Popup {
	constructor(opts){
		super(opts);
		opts.fps = opts.fps || 30;
		this.fps_period = 1 / this.fps;
		this.task = null;
		this.stream = null;
		this.normal_stop = false;
		this.mimetype = 'audio/wav';
		this.preview = new bricks.VBox({width: '100%', css: 'filler'});
		this.controls = new bricks.HBox({width: '100%', cheight: 2.5});
		this.toggle_record = new bricks.Svg({
			url: bricks_resource('/imgs/start_recording.svg'),
			tip: 'start or stop record',
			rate: 2
		});
		this.timepass = new bricks.Text({text:'00:00:00', cheight: 1.2});
		var filler = new bricks.Filler({});
		var cancel = new bricks.Svg({
						url: bricks_resource('imgs/delete.svg'), 
						rate:2,
						tip: 'cancel recording'});
		cancel.bind('click', this.cancel_record.bind(this))
		this.add_widget(this.preview);
		this.add_widget(this.controls);
		this.controls.add_widget(this.toggle_record);
		this.controls.add_widget(this.timepass);
		this.controls.add_widget(filler);
		this.controls.add_widget(cancel);
		this.record_status = 'standby';
		this.toggle_record.bind('click', this.switch_record.bind(this));
		this.toggle_record.disabled(true);
		schedule_once(this.open_recorder.bind(this), 0.1);
	}
	async tick_task(){
		this.timepass.set_text(bricks.timeDiff(this.start_time));
	}
	async switch_record(){
		console.log('toggle_record called');
		if (this.record_status == 'standby'){
			this.start_record();
			this.toggle_record.set_url(bricks_resource('imgs/stop_recording.svg'));
			this.record_status = 'recording';
		} else {
			this.stop_record();
			this.toggle_record.set_url(bricks_resource('imgs/start_recording.svg'));
			this.record_status = 'standby';
		}
	}
	cancel_record(){
		this.close_recorder();
	}
	async open_recorder(){
		console.debug('open recorder for record');
	}
	async start_record(){
		this.normal_stop = false;
		this.mediaRecorder = new MediaRecorder(this.stream, 
								{mimeType: this.mimetype});
		this.recordedChunks = [];
		this.mediaRecorder.ondataavailable = (event) => {
			console.log('ondataavailabe() called', event.data.size);
			if (event.data.size > 0) {
				this.time_diff = bricks.timeDiff(this.start_time);
				this.recordedChunks.push(event.data);
				this.timepass.set_text(this.time_diff);
			}
		};
		this.mediaRecorder.onstop = async () => {
			console.log('onstop() called', this.normal_stop);
			if (!this.normal_stop) return;
			var blob = new Blob(this.recordedChunks, 
							{ type: this.mimetype }); 
			// 1. 在本地播放
			blob = await this.blob_convert(blob);
			const url = URL.createObjectURL(blob);
			// 2. 转成 File 对象
			var filename;
			if (this.mimetype == 'video/mp4'){
				filename = 'recorded_video.mp4';
			} else {
				filename = 'recorded_audio.wav'
			}
			const file = new File([blob], 
						filename, 
						{ type: this.mimetype });
			var data = {
				url: url,
				file: file
			}
			this.dispatch('record_end', data);
			console.log('"record_end" fired', file);
		};

		this.start_time = Date.now();
		this.task = schedule_interval(this.tick_task.bind(this), 0.5);
		this.mediaRecorder.start();
		this.dispatch('record_started')
		console.log("Recording started...");
	}
	async blob_convert(blob){
		return blob;
	}
	stop_record(){
		if (this.task){
			clearInterval(this.task);
			this.task = null;
		}
		this.normal_stop = true;
		this.time_diff = bricks.timeDiff(this.start_time);
		this.timepass.set_text(this.time_diff);
		this.mediaRecorder.stop();
		this.mediaRecorder = null;
		this.close_recorder();
		console.log("Recording stopped.");
	}

	close_recorder(){
		if (this.stream){
			if (this.mediaRecorder){
				this.mediaRecorder.stop();
				this.mediaRecorder = null;
			}
			this.stream.getTracks().forEach(track => track.stop());
			this.stream = null;
		}
		this.dismiss();
	}
}

bricks.WidgetRecorder = class extends bricks.MediaRecorder {
	async open_recorder(){
		var widget = bricks.getWidgetById(this.opts.widgetid,bricks.app);
		if (widget.dom_element.tagName == 'VIDEO'){
			this.mimetype = 'video/mp4';
		} else if (widget.dom_element.tagName == 'AUDIO'){
			this.mimetype = 'audio/wav';
		} else {
			throw 'Error, not a media element';
		}
		this.stream = source.captureStream();
		this.toggle_record.disabled(false);
	}
}
bricks.SysAudioRecorder = class extends bricks.MediaRecorder {
	async open_recorder(){
		var options = {}
		options.audio = true;
		this.mimetype = 'audio/webm';
		this.stream = await navigator.mediaDevices.getUserMedia(options);
		this.toggle_record.disabled(false);
		this.preview.disabled(true);
	}
	async blob_convert(webmBlob){
		// 2. 解码 WebM → PCM
		const arrayBuffer = await webmBlob.arrayBuffer();
		const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
		const decodedData = await audioCtx.decodeAudioData(arrayBuffer);

		// 3. 转换为 WAV Blob
		const wavBlob = this.encodeWAV(decodedData);
		return wavBlob;
	}
	encodeWAV(audioBuffer) {
		const numChannels = audioBuffer.numberOfChannels;
		const sampleRate = audioBuffer.sampleRate;
		const format = 1; // PCM
		const bitDepth = 16;

		// interleave channels
		let result;
		if (numChannels === 2) {
			result = this.interleave(audioBuffer.getChannelData(0), audioBuffer.getChannelData(1));
		} else {
			result = audioBuffer.getChannelData(0);
		}

		// float → 16-bit PCM
		const buffer = new ArrayBuffer(44 + result.length * 2);
		const view = new DataView(buffer);

		/* RIFF header */
		this.writeString(view, 0, "RIFF");
		view.setUint32(4, 36 + result.length * 2, true);
		this.writeString(view, 8, "WAVE");
		this.writeString(view, 12, "fmt ");
		view.setUint32(16, 16, true);
		view.setUint16(20, format, true);
		view.setUint16(22, numChannels, true);
		view.setUint32(24, sampleRate, true);
		view.setUint32(28, sampleRate * numChannels * (bitDepth / 8), true);
		view.setUint16(32, numChannels * (bitDepth / 8), true);
		view.setUint16(34, bitDepth, true);

		this.writeString(view, 36, "data");
		view.setUint32(40, result.length * 2, true);

		// PCM samples
		let offset = 44;
		for (let i = 0; i < result.length; i++, offset += 2) {
			const s = Math.max(-1, Math.min(1, result[i]));
			view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
		}
		return new Blob([view], { type: "audio/wav" });
	}

	interleave(left, right) {
		const length = left.length + right.length;
		const result = new Float32Array(length);

		let inputIndex = 0;
		for (let i = 0; i < length;) {
			result[i++] = left[inputIndex];
			result[i++] = right[inputIndex];
			inputIndex++;
		}
		return result;
	}

	writeString(view, offset, string) {
		for (let i = 0; i < string.length; i++) {
			view.setUint8(offset + i, string.charCodeAt(i));
		}
	}
}
bricks.SysVideoRecorder = class extends bricks.MediaRecorder {
	async open_recorder(){
		var options = {
			audio: true,
			video: true
		}
		this.mimetype = 'video/mp4';
		this.stream = await navigator.mediaDevices.getUserMedia(options);
		const track = this.stream.getVideoTracks()[0];
		const settings = track.getSettings();
		this.imageCapture = new ImageCapture(track);
		this.camera_height = settings.height;
		this.camera_width = settings.width;
		this.imgw = new bricks.Image({width: '100%'});
		this.preview.add_widget(this.imgw);
		this.toggle_record.disabled(false);
		this.fps_task = schedule_interval(this.show_picture.bind(this), this.fps_period);
	}
	async show_picture(){
		try {
			var blob = await this.imageCapture.takePhoto();
			this.dataurl = URL.createObjectURL(blob);
			this.imgfile = new File([blob],
							'image.jpg',
							{ type: 'image/jpeg' });
			this.imgw.set_url(this.dataurl);
		} catch(e){
			;
		}
	}
	close_recorder(){
		super.close_recorder();
		if (this.fps_task){
			clearInterval(this.fps_task);
			this.fps_task = null;
		}
	}
}
bricks.SysCamera= class extends bricks.SysVideoRecorder {
	switch_record(){
		console.log('shot it ............');
		event.stopPropagation();
		this.dispatch('shot', {url: this.dataurl, file:this.imgfile});
		this.close_recorder();
	}
}
bricks.Factory.register('SysCamera', bricks.SysCamera);
bricks.Factory.register('WidgetRecorder', bricks.WidgetRecorder);
bricks.Factory.register('SysAudioRecorder', bricks.SysAudioRecorder);
bricks.Factory.register('SysVideoRecorder', bricks.SysVideoRecorder);
