using Gst;

public class AudioMixer {
	private string master_file;
	private string output_file;

	private int audio_bitrate = 128;
	private int audio_quality = 9; // 0-9; 9 is worst, but fastest
	
	private MainLoop loop = new MainLoop (null, false);
	private Pipeline pipe = new Pipeline("pipeline");
	private int fakesink_count = 0;

	private Element master_demux;
	private Pad master_demux_pad;
	private Bin audio_bin;
	
	public AudioMixer(string master_file, string output_file) {
		this.master_file = master_file;
		this.output_file = output_file;
	}
	
	private void on_master_demux_pad_added(Element demux, Pad demux_pad) {
		Caps caps = demux_pad.get_caps();
		Structure struct = caps.get_structure(0);
		if (struct.get_name().has_prefix("audio/")) {
			// TODO allow only one audio track
			
			master_demux_pad = demux_pad;
			audio_bin = new Bin("audio_bin");
			pipe.add(audio_bin);

			// create elements
			Element audioconvert = ElementFactory.make("audioconvert", "audioconvert");
			Element resample = ElementFactory.make("audioresample", "audioresample");
			dynamic Element lame = ElementFactory.make("lame", "lame");
			lame.quality = audio_quality;
			lame.bitrate = audio_bitrate;
			Element muxer = ElementFactory.make("flvmux", "muxer");
			dynamic Element file_sink = ElementFactory.make("filesink", "fileSink");
			file_sink.location = output_file;

			// add elements to bin
			audio_bin.add_many(audioconvert, resample, lame, muxer, file_sink);

			// link pads
			demux.link_pads(demux_pad.get_name(), audioconvert, "sink");
			audioconvert.link_many(resample, lame, muxer, file_sink);

			// add handlers
			pipe.get_bus().message["state-changed"].connect(on_state_changed);
			pipe.get_bus().message["segment-done"].connect(on_segment_done);
			
			audio_bin.set_state(State.PAUSED);
		} else {
			// ignore all other streams (send them to a fake sink)
		    Bin fake_bin = new Bin(@"fakeBin$fakesink_count");
 			pipe.add(fake_bin);
 			Element fakesink = ElementFactory.make("fakesink", @"fakesink$fakesink_count");
			fakesink_count++;
 			fake_bin.add(fakesink);
 			demux.link_pads(demux_pad.get_name(), fakesink, "sink");
 			fake_bin.set_state(State.PAUSED);
		}
	}

	private void on_error(Gst.Bus bus, Gst.Message message) {
		GLib.Error err;
		string debug;
		message.parse_error (out err, out debug);
		stderr.printf ("Error: %s\n", err.message);
		pipe.set_state (State.NULL);
		loop.quit ();
	}

	private void on_eos(Gst.Bus bus, Gst.Message message) {
		pipe.set_state (State.NULL);
		loop.quit ();
	}

	void on_state_changed(Gst.Bus bus, Gst.Message message) {
		Gst.State old_state;
		Gst.State new_state;
		Gst.State pending;

		message.parse_state_changed(out old_state, out new_state, out pending);
		
		if (message.src == master_demux && old_state == State.READY && new_state == State.PAUSED) {
			stdout.printf("sending seek event...\n");
			master_demux_pad.send_event(new Event.seek(1.0, Format.TIME, SeekFlags.SEGMENT | SeekFlags.FLUSH | SeekFlags.ACCURATE, Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1));
		}
	}

	void on_segment_done(Gst.Bus bus, Gst.Message message) {
		stdout.printf("End of segment reached, resending seek event...\n");
		master_demux_pad.send_event(new Event.seek(1.0, Format.TIME, SeekFlags.SEGMENT | SeekFlags.FLUSH | SeekFlags.ACCURATE, Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1));
	}
	
	public void run() {
		dynamic Element src = ElementFactory.make("filesrc", "src");
		src.location = master_file;
		master_demux = ElementFactory.make("decodebin2", "master_demux");
		pipe.add_many(src, master_demux);
		src.link(master_demux);

		master_demux.pad_added.connect(on_master_demux_pad_added);

		Bus bus = pipe.get_bus ();
		bus.add_signal_watch();
		bus.message["error"].connect(on_error);
		bus.message["eos"].connect(on_eos);
		
		pipe.set_state (State.PLAYING);

		loop.run ();
	}
}

static int main(string[] args) {
 
	Gst.init(ref args);
 
	if (args.length < 3) {
		stdout.printf("Please pass the following arguments: inputFile flvOutputFile\n");
		return -1;
	}

	string input_file = args[1];
	string output_file = args[2];

	AudioMixer mixer = new AudioMixer(input_file, output_file);

	mixer.run();
	
    return 0;
}

