#include "main_window.h"
#include <Button.h>
#include <Slider.h>
#include <Message.h>
#include <GroupView.h>
#include <Messenger.h>
#include <MessageRunner.h>
#include <MenuBar.h>
#include <Path.h>
#include <Menu.h>
#include <MessageFilter.h>
#include <functional>
#include <util.hpp>
#include <MenuItem.h>
#include <thread>
#include <log.hpp>
#include <cmath>
#include <fmt/core.h>
#include <fmt/format.h>
#include <SDL.h>
#include "icons.h"
#include "utils.h"
#include "aboutwindow.h"
#define CMD_MOUSE_DOWN_KEY "catmeow:pressed"
#define CMD_PAUSE_RESUME 0x10
#define CMD_RESTART 0x11
#define CMD_STOP 0x12
#define CMD_OPEN 0x13
#define CMD_ABOUT 0x14
#define CMD_PREFS 0x15
#define CMD_SEEK 0x20
#define CMD_VOLUME 0x21
#define CMD_SPEED 0x22
#define CMD_PITCH 0x23
#define CMD_TEMPO 0x24
#define CMD_UPDATE 0x80
#define SLIDER_SCALE 1000
#define D(x) ((double)(x))
std::vector<Subwindow*> Subwindow::windows;
static BMessage *make_slider_msg(uint32_t what, bool down) {
	BMessage *msg = new BMessage(what);
	msg->SetBool(CMD_MOUSE_DOWN_KEY, down);
	return msg;
}
static bool is_slider_down_msg(BMessage *msg) {
	return msg->HasBool(CMD_MOUSE_DOWN_KEY) && msg->GetBool(CMD_MOUSE_DOWN_KEY);
}
void HaikuLooperWindow::UpdateViewFlags(BView *view) {
	if (view == NULL) return;
	view->SetFlags(view->Flags()|B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
	view->SetResizingMode(B_FOLLOW_ALL_SIDES);
}
void HaikuLooperWindow::UpdateViewFlags(BLayout *layout) {
	BView *owner = layout->Owner();
	if (owner == NULL) {
		owner = layout->View();
		if (owner == NULL) {
			return;
		}
	}
	UpdateViewFlags(owner);
}
HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
	pause_bitmap = load_icon(ICON_PAUSE);
	resume_bitmap = load_icon(ICON_PLAY);
	stop_bitmap = load_icon(ICON_STOP);
	refresh_bitmap = load_icon(ICON_REFRESH);
    ref_handler = new HaikuLooperRefHandler(this);
    this->playback = playback;
    float minW, minH, maxW, maxH;
    GetSizeLimits(&minW, &maxW, &minH, &maxH);
    minW = 600;
    minH = 200;
    SetSizeLimits(minW, maxW, minH, maxH);
    ResizeTo(minW, minH);
    DisableUpdates();
    auto about_window = new HaikuAboutWindow();
    layout = new BGroupLayout(B_VERTICAL);
    SetLayout(layout);
    layout->SetSpacing(0.0);
    layout->SetInsets(0, 0, 0, 0);
    BMenuBar *menu_bar = new BMenuBar("main_menu");
    file_menu = new BMenu("File");
    open_item = new BMenuItem("Open...", new BMessage(CMD_OPEN));
    prefs_item = new BMenuItem("Preferences...", new BMessage(CMD_PREFS));
    quit_item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED));
    help_menu = new BMenu("Help");
    about_item = new BMenuItem("About...", new BMessage(CMD_ABOUT));
    file_menu->AddItem(open_item);
    file_menu->AddItem(prefs_item);
    file_menu->AddItem(quit_item);
    help_menu->AddItem(about_item);
    menu_bar->AddItem(file_menu);
    menu_bar->AddItem(help_menu);
    layout->AddView(menu_bar);
    BView *spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
    spacer->SetExplicitPreferredSize(BSize(0, 0));
    spacer->SetExplicitMinSize(BSize(0, 0));
    layout->AddView(spacer);
    BGroupView *top_row = new BGroupView(B_HORIZONTAL);
    BGroupView *bottom_row = new BGroupView(B_HORIZONTAL);
    BLayoutItem *top_item = layout->AddView(top_row);
    BLayoutItem *bottom_item = layout->AddView(bottom_row);
    pause_resume_btn = new BButton(NULL, new BMessage(CMD_PAUSE_RESUME));
    pause_resume_btn->SetTarget(this);
    top_row->AddChild(pause_resume_btn);
    restart_btn = new BButton(NULL, new BMessage(CMD_RESTART));
    restart_btn->SetTarget(this);
    top_row->AddChild(restart_btn);
    slider = new BSlider("seek_slider", "Seek", make_slider_msg(CMD_SEEK, false), 0, INT32_MAX, B_HORIZONTAL);
    slider->SetModificationMessage(make_slider_msg(CMD_SEEK, true));
    slider->SetTarget(this);
    top_row->AddChild(slider);
    stop_btn = new BButton(NULL, new BMessage(CMD_STOP));
    stop_btn->SetTarget(this);
    top_row->AddChild(stop_btn);
    volume_slider = new HaikuLooperSlider("volume", "Volume", new BMessage(CMD_VOLUME), 0, 0, 100, 1, false);
    volume_slider->SetLimitLabels("Muted", "Full Volume");
    volume_slider->SetTarget(this);
    top_row->AddChild(volume_slider);
    speed_slider = new HaikuLooperSlider("speed", "Speed", new BMessage(CMD_SPEED), 0, 0.25, 4.0, 0.01, true);
    speed_slider->SetLimitLabels("0.25x", "4.00x");
    speed_slider->SetTarget(this);
    bottom_row->AddChild(speed_slider);
    tempo_slider = new HaikuLooperSlider("tempo", "Tempo", new BMessage(CMD_TEMPO), 0, 0.25, 4.0, 0.01, true);
    tempo_slider->SetLimitLabels("0.25x", "4.00x");
    tempo_slider->SetTarget(this);
    bottom_row->AddChild(tempo_slider);
    pitch_slider = new HaikuLooperSlider("pitch", "Pitch", new BMessage(CMD_PITCH), 0, 0.25, 4.0, 0.01, true);
    pitch_slider->SetLimitLabels("0.25x", "4.00x");
    pitch_slider->SetTarget(this);
    bottom_row->AddChild(pitch_slider);
    file_panel = new BFilePanel(B_OPEN_PANEL, new BMessenger(this));
    auto prefs_window = new HaikuPrefsWindow(this);
    prefs_subwindow = Subwindow::Add(prefs_window);
    about_subwindow = Subwindow::Add(about_window);
    EnableUpdates();
    InvalidateLayout(true);
    BRect top_rect = top_item->Frame();
    top_item->SetExplicitMinSize(top_rect.Size());
	BRect bottom_rect = bottom_item->Frame();
	bottom_item->SetExplicitMinSize(bottom_rect.Size());
    Pulse();
    this->update_thread = new std::thread(std::mem_fn(&HaikuLooperWindow::ThreadFunc), this);
    UpdateIfNeeded();
    StartWatching(prefs_window, CMD_UPDATE_LABEL_SETTING);
    auto *msg = new BMessage(CMD_UPDATE_LABEL_SETTING);
    MessageReceived(msg);
    delete msg;
}
HaikuLooperWindow::~HaikuLooperWindow() {
	delete ref_handler;
	delete pause_bitmap;
	delete resume_bitmap;
	delete stop_bitmap;
	delete refresh_bitmap;
    playback->Stop();
    done = true;
    update_thread->join();
    delete update_thread;
}
void HaikuLooperWindow::MessageReceived(BMessage *msg) {
    SDL_Event ev;
    while (SDL_PollEvent(&ev)) {
    	if (ev.type == SDL_DROPFILE) {
    		playback->Start(ev.drop.file);
    	} else if (ev.type == SDL_QUIT) {
    		Hide();
    		return;
    	}
    }
    if (msg->what == B_REFS_RECEIVED) {
        entry_ref ref;
        if (msg->FindRef("refs", &ref) != B_OK) { // Don't crash when failing to get ref - This is probably an error in the system or another application
            return;
        }
        BEntry entry(&ref, true);
        BPath path;
        if (entry.GetPath(&path) != B_OK) { // Don't know how this could cause an error, but don't crash if it does.
            return;
        }
        playback->Start(path.Path());
        return;
    }
    if (msg->IsSystem()) {
    	switch (msg->what) {
    		default: {
		    	msg->PrintToStream();
    		} break;
    	}
    	return;
    }
    switch (msg->what) {
    case CMD_PAUSE_RESUME: {
        playback->Pause();
    } break;
    case CMD_RESTART: {
        playback->Seek(0.0);
    } break;
    case CMD_VOLUME: {
        playback->SetVolume(msg->GetDouble("be:value", 100));
        volume_clicked = is_slider_down_msg(msg);
    } break;
    case CMD_SPEED: {
        playback->SetSpeed(msg->GetDouble("be:value", 1.0));
        speed_clicked = is_slider_down_msg(msg);
    } break;
    case CMD_TEMPO: {
        playback->SetTempo(msg->GetDouble("be:value", 1.0));
        tempo_clicked = is_slider_down_msg(msg);
    } break;
    case CMD_PITCH: {
        playback->SetPitch(msg->GetDouble("be:value", 1.0));
        pitch_clicked = is_slider_down_msg(msg);
    } break;
    case CMD_SEEK: {
        playback->Seek((((double)msg->GetInt32("be:value", 0)) / INT32_MAX) * playback->GetLength());
        seek_clicked = is_slider_down_msg(msg);
    } break;
    case CMD_UPDATE: {
        Pulse();
    } break;
    case CMD_STOP: {
        playback->Stop();
    } break;
    case CMD_OPEN: {
        file_panel->Show();
    } break;
    case CMD_ABOUT: {
    	about_subwindow->Show();
    } break;
    case CMD_PREFS: {
    	prefs_subwindow->Show();
    } break;
    case CMD_UPDATE_LABEL_SETTING: {
    	volume_slider->MessageReceived(msg);
    	speed_slider->MessageReceived(msg);
    	tempo_slider->MessageReceived(msg);
    	pitch_slider->MessageReceived(msg);
    	if (show_labels) {
    		stop_btn->SetLabel("Stop");
    		restart_btn->SetLabel("Restart");
    	} else {
    		stop_btn->SetLabel("");
    		restart_btn->SetLabel("");
    	}
    	if (show_icons) {
    		stop_btn->SetIcon(stop_bitmap);
    		restart_btn->SetIcon(refresh_bitmap);
    	} else {
    		stop_btn->SetIcon(get_empty_icon());
    		restart_btn->SetIcon(get_empty_icon());
    	}
    } break;
    case 'DATA': {
    	status_t err = B_OK;
        entry_ref ref;
        if ((err = msg->FindRef("refs", &ref)) == B_OK) {
            BEntry entry(&ref);
            BPath path;
            if ((err = entry.GetPath(&path)) == B_OK) {
                    playback->Start(path.Path());
            } else {
		    	WARNING.writefln("entry.GetPath(&path) == %d", (int32)(err));
		    	msg->PrintToStream();
            }
        } else {
		    WARNING.writefln("msg->FindRef(\"refs\", &ref) == %d", (int32)(err));
		    msg->PrintToStream();
        }
    } break;
    default: {
    	if (msg->HasPointer("catmeow:target")) {
    		BHandler *ptr = (BHandler*)msg->GetPointer("catmeow:target");
    		ptr->MessageReceived(msg);
    	} else {
	    	WARNING.writeln("Unrecognized message type");
	    	msg->PrintToStream();
    	}
    } break;
    };
}
void HaikuLooperWindow::Pulse() {
    auto len = playback->GetLength();
    auto pos = playback->GetPosition();
    auto pos_milliseconds = pos * 1000.0;
    int32_t pos_int32 = (int32_t)((((int64_t)pos_milliseconds) * INT32_MAX) / 1000 / len);
	if (!seek_clicked) slider->SetValue(pos_int32);
    auto component_count = TimeToComponentCount(len);
    bool enable_ui = !playback->IsStopped();
    if (enable_ui) {
	    slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
	    slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str());
	    if (show_icons) pause_resume_btn->SetIcon(playback->IsPaused() ? resume_bitmap : pause_bitmap);
	    if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause");
    } else {
    	slider->SetLabel("Position");
    	slider->SetLimitLabels("N/A", "N/A");
    	if (show_icons) pause_resume_btn->SetIcon(pause_bitmap);
    	if (show_labels) pause_resume_btn->SetLabel("Pause");
    }
    if (!show_icons) pause_resume_btn->SetIcon(get_empty_icon());
    if (!show_labels) pause_resume_btn->SetLabel("");
    slider->SetEnabled(enable_ui);
    pause_resume_btn->SetEnabled(enable_ui);
    auto volume = playback->GetVolume();
    auto pitch = playback->GetPitch();
    auto speed = playback->GetSpeed();
    auto tempo = playback->GetTempo();
    if (!volume_clicked) volume_slider->SetValue(volume);
    volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
    if (!pitch_clicked) pitch_slider->SetValueDouble(pitch);
    pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
    if (!speed_clicked) speed_slider->SetValueDouble(speed);
    speed_slider->SetLabel(fmt::format("Speed: {:.02f}x", speed).c_str());
    if (!tempo_clicked) tempo_slider->SetValueDouble(tempo);
    tempo_slider->SetLabel(fmt::format("Tempo: {:.02f}x", tempo).c_str());
    UpdateIfNeeded();
    auto title = playback->get_current_title();
    if (title.has_value()) {
	    SetTitle(fmt::format("{} - Looper", title.value()).c_str());
    } else {
	    SetTitle("Looper");
    }
}
void HaikuLooperWindow::FrameResized(float newWidth, float newHeight) {
	InvalidateLayout(true);
	Layout(true);
	layout->SetExplicitSize(BSize(newWidth, newHeight));
}
void HaikuLooperWindow::ThreadFunc() {
    while (true) {
        if (done) return;
        if (Lock()) {
            if (!IsHidden()) {
                Unlock();
                break;
            }
            Unlock();
        } else {
            break;
        }
    }
    while (true) {
        if (done) return;
        if (Lock()) {
            if (IsHidden()) {
                Unlock();
                break;
            }
            PostMessage(CMD_UPDATE);
            Unlock();
        } else {
            break;
        }
        std::this_thread::sleep_for(1s / 60.0);
    }
}
HaikuLooperRefHandler::HaikuLooperRefHandler(HaikuLooperWindow *win) {
	this->win = win;
	be_app->Lock();
	this->next_handler = be_app->PreferredHandler();
	be_app->AddHandler(this);
	be_app->SetPreferredHandler(this);
	if (next_handler != NULL) {
		SetNextHandler(next_handler);
	}
	AddFilter(new BMessageFilter(B_REFS_RECEIVED));
	be_app->Unlock();
}
void HaikuLooperRefHandler::MessageReceived(BMessage *msg) {
	if (msg->what == B_REFS_RECEIVED) {
		win->PostMessage(msg);
	}
}