#include <psemek/audio/engine.hpp>
#include <psemek/audio/wave/silence.hpp>
#include <psemek/audio/wave/sine.hpp>
#include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/wave/karplus_strong.hpp>
#include <psemek/audio/effect/volume.hpp>
#include <psemek/audio/effect/fade_in.hpp>
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/effect/compressor.hpp>
#include <psemek/audio/effect/pause.hpp>
#include <psemek/audio/combine/loop.hpp>
#include <psemek/audio/effect/pitch.hpp>
#include <psemek/audio/effect/distortion.hpp>
#include <psemek/audio/combine/duplicate.hpp>
#include <psemek/audio/combine/stereo.hpp>
#include <psemek/audio/combine/mixer.hpp>
#include <psemek/audio/midi.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/math/constants.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/io/file_stream.hpp>

#include <map>

using namespace psemek;

static std::map<app::keycode, int> const key_to_midi
{
	{app::keycode::Z, 59},
	{app::keycode::X, 60},
	{app::keycode::C, 61},
	{app::keycode::V, 62},
	{app::keycode::B, 63},
	{app::keycode::N, 64},
	{app::keycode::M, 65},
	{app::keycode::COMMA, 66},
	{app::keycode::PERIOD, 67},
	{app::keycode::SLASH, 68},
	{app::keycode::A, 69},
	{app::keycode::S, 70},
	{app::keycode::D, 71},
	{app::keycode::F, 72},
	{app::keycode::G, 73},
	{app::keycode::H, 74},
	{app::keycode::J, 75},
	{app::keycode::K, 76},
	{app::keycode::L, 77},
	{app::keycode::SEMICOLON, 78},
	{app::keycode::APOSTROPHE, 79},
	{app::keycode::Q, 80},
	{app::keycode::W, 81},
	{app::keycode::E, 82},
	{app::keycode::R, 83},
	{app::keycode::T, 84},
	{app::keycode::Y, 85},
	{app::keycode::U, 86},
	{app::keycode::I, 87},
	{app::keycode::O, 88},
	{app::keycode::P, 89},
	{app::keycode::LEFTBRACKET, 90},
	{app::keycode::RIGHTBRACKET, 91},
};

static math::interval<int> const key_rows[3] = {
	{59, 68},
	{69, 79},
	{80, 91},
};

static std::string_view const midi_name[12] = {
	"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#",
};

struct audio_app
	: app::application_base
{
	audio_app(options const &, context const &)
	{
		engine_ = audio::make_engine();
		mixer_ = audio::make_mixer();

		volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
		pitch_control_ = audio::pitch(volume_control_, 1.f, 0.025f);
		auto compressor = audio::compressor(pitch_control_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
		pause_control_ = audio::pause(compressor, false, 0.01f);
		engine_->output()->stream(pause_control_);
	}

	void on_event(app::key_event const & event) override
	{
		app::application_base::on_event(event);

		if (event.down && key_to_midi.contains(event.key))
		{
			int midi = key_to_midi.at(event.key);
			if (!channels_.contains(midi))
			{
				auto tone = audio::karplus_strong(audio::midi_frequency(midi));
				tone = audio::distortion(std::move(tone), 4.f);
				channels_[midi] = mixer_->add(audio::fade_in(tone, 0.005f));
			}
		}

		if (!event.down && key_to_midi.contains(event.key))
		{
			int midi = key_to_midi.at(event.key);
			auto & ch = channels_[midi];
			if (auto s = ch->stream())
				ch->stream(audio::fade_out(s, 0.1f));
			channels_.erase(midi);
		}

		if (event.down && event.key == app::keycode::SPACE)
			pause_control_->paused(!pause_control_->paused());

		if (event.down && event.key == app::keycode::KP_PLUS)
			pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));

		if (event.down && event.key == app::keycode::KP_MINUS)
			pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));

		if (!event.down && event.key == app::keycode::KP_PLUS)
			pitch_control_->pitch(1.f);

		if (!event.down && event.key == app::keycode::KP_MINUS)
			pitch_control_->pitch(1.f);
	}

	void update() override
	{
		float const dt = clock_.restart().count();
		time_ += dt;

		smooth_pitch_ += (12.f * std::log2(pitch_control_->pitch()) - smooth_pitch_) * (1.f - std::exp(- 20.f * dt));

		float volume = std::sin(time_);
		volume_control_->gain_left(0.5f + 0.5f * volume);
		volume_control_->gain_right(0.5f - 0.5f * volume);
	}

	void present() override
	{
		gl::ClearColor(0.8f, 0.8f, 1.f, 1.f);
		gl::Clear(gl::COLOR_BUFFER_BIT);

		float size = 64.f;
		float margin = 4.f;
		float border = 8.f;

		float center = state().size[0] / 2.f;
		float y = state().size[1] / 2.f + size * std::size(key_rows) / 2.f;

		gfx::painter::text_options opts;
		opts.scale = {2.f, 2.f};
		opts.c = {0, 0, 0, 255};
		opts.x = gfx::painter::x_align::center;
		opts.y = gfx::painter::y_align::center;

		for (auto const & row : key_rows)
		{
			y -= size;

			float x = center - (row.max - row.min + 1) * size / 2.f;

			for (int i = 0; i <= row.max - row.min; ++i)
			{
				gfx::color_rgba color{0, 0, 0, 255};
				if (channels_.contains(row.min + i))
					color = {255, 127, 0, 255};
				painter_.rect({{{x + margin, x + size - margin}, {y + margin, y + size - margin}}}, color);

				color = {255, 255, 255, 255};
				painter_.rect({{{x + border, x + size - border}, {y + border, y + size - border}}}, color);

				painter_.text({x + size / 2.f, y + size / 2.f}, midi_name[(row.min + i + 3) % 12], opts);

				x += size;
			}
		}

		opts.scale = {4.f, 4.f};
		opts.c = pause_control_->paused() ? gfx::color_rgba{255, 0, 0, 255} : gfx::color_rgba{0, 127, 0, 255};
		painter_.text({state().size[0] / 2.f, state().size[1] - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts);

		{
			float x = state().size[0] - 200.f;
			float y = state().size[1] / 2.f;

			float w = 4.f;
			float h = 64.f;

			float s = 16.f;
			float r = y - h * smooth_pitch_;

			painter_.rect({{{x - w, x + w}, {y - h, y + h}}}, {0, 0, 0, 255});

			painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255});
		}

		painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
	}

	~audio_app() override
	{
		prof::dump();
	}

private:
	std::unique_ptr<audio::engine> engine_;
	audio::mixer_ptr mixer_;
	std::shared_ptr<audio::volume_control_stereo> volume_control_;
	std::shared_ptr<audio::pitch_control> pitch_control_;
	std::shared_ptr<audio::pause_control> pause_control_;
	std::map<int, audio::channel_ptr> channels_;

	float smooth_pitch_ = 0.f;

	util::clock<> clock_;
	float time_ = 0.f;

	gfx::painter painter_;
};

namespace psemek::app
{

	std::unique_ptr<application::factory> make_application_factory()
	{
		return default_application_factory<audio_app>({.name = "Audio example"});
	}

}
