/*
 * Copyright (c) 2004 Matthijs Hollemans
 * Copyright (c) 2008-2014 Haiku, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
 
 
#include "MidiPlayerWindow.h"
 
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MidiProducer.h>
#include <MidiRoster.h>
#include <SeparatorView.h>
#include <StorageKit.h>
#include <SpaceLayoutItem.h>
 
#include "MidiPlayerApp.h"
#include "ScopeView.h"
#include "SynthBridge.h"
 
 
#define _W(a) (a->Frame().Width())
#define _H(a) (a->Frame().Height())
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Main Window"
 
 
//	#pragma mark - MidiPlayerWindow
 
 
MidiPlayerWindow::MidiPlayerWindow()
	:
	BWindow(BRect(0, 0, 1, 1), B_TRANSLATE_SYSTEM_NAME("MidiPlayer"),
		B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE
			| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
{
	fIsPlaying = false;
	fScopeEnabled = true;
	fReverbMode = B_REVERB_BALLROOM;
	fVolume = 75;
	fWindowX = -1;
	fWindowY = -1;
	fInputId = -1;
	fInstrumentLoaded = false;
 
	be_synth->SetSamplingRate(44100);
 
	fSynthBridge = new SynthBridge;
	//fSynthBridge->Register();
 
	CreateViews();
	LoadSettings();
	InitControls();
}
 
 
MidiPlayerWindow::~MidiPlayerWindow()
{
	StopSynth();
 
	//fSynthBridge->Unregister();
	fSynthBridge->Release();
}
 
 
bool
MidiPlayerWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}
 
 
void
MidiPlayerWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_PLAY_STOP:
			OnPlayStop();
			break;
 
		case MSG_SHOW_SCOPE:
			OnShowScope();
			break;
 
		case MSG_INPUT_CHANGED:
			OnInputChanged(message);
			break;
 
		case MSG_REVERB_NONE:
			OnReverb(B_REVERB_NONE);
			break;
 
		case MSG_REVERB_CLOSET:
			OnReverb(B_REVERB_CLOSET);
			break;
 
		case MSG_REVERB_GARAGE:
			OnReverb(B_REVERB_GARAGE);
			break;
 
		case MSG_REVERB_IGOR:
			OnReverb(B_REVERB_BALLROOM);
			break;
 
		case MSG_REVERB_CAVERN:
			OnReverb(B_REVERB_CAVERN);
			break;
 
		case MSG_REVERB_DUNGEON:
			OnReverb(B_REVERB_DUNGEON);
			break;
 
		case MSG_VOLUME:
			OnVolume();
			break;
 
		case B_SIMPLE_DATA:
			OnDrop(message);
			break;
 
		default:
			super::MessageReceived(message);
			break;
	}
}
 
 
void
MidiPlayerWindow::FrameMoved(BPoint origin)
{
	super::FrameMoved(origin);
	fWindowX = Frame().left;
	fWindowY = Frame().top;
	SaveSettings();
}
 
 
void
MidiPlayerWindow::MenusBeginning()
{
	for (int32 t = fInputPopUpMenu->CountItems() - 1; t > 0; --t)
		delete fInputPopUpMenu->RemoveItem(t);
 
	// Note: if the selected endpoint no longer exists, then no endpoint is
	// marked. However, we won't disconnect it until you choose another one.
 
	fInputOffMenuItem->SetMarked(fInputId == -1);
 
	int32 id = 0;
	while (BMidiEndpoint* endpoint = BMidiRoster::NextEndpoint(&id)) {
		if (endpoint->IsProducer()) {
			BMessage* message = new BMessage(MSG_INPUT_CHANGED);
			message->AddInt32("id", id);
 
			BMenuItem* item = new BMenuItem(endpoint->Name(), message);
			fInputPopUpMenu->AddItem(item);
			item->SetMarked(fInputId == id);
		}
 
		endpoint->Release();
	}
}
 
 
void
MidiPlayerWindow::CreateInputMenu()
{
	fInputPopUpMenu = new BPopUpMenu("inputPopUp");
 
	BMessage* message = new BMessage(MSG_INPUT_CHANGED);
	message->AddInt32("id", -1);
 
	fInputOffMenuItem = new BMenuItem(B_TRANSLATE("Off"), message);
	fInputPopUpMenu->AddItem(fInputOffMenuItem);
 
	fInputMenuField = new BMenuField(B_TRANSLATE("Live input:"),
		fInputPopUpMenu);
}
 
 
void
MidiPlayerWindow::CreateReverbMenu()
{
	BPopUpMenu* reverbPopUpMenu = new BPopUpMenu("reverbPopUp");
	fReverbNoneMenuItem = new BMenuItem(B_TRANSLATE("None"),
		new BMessage(MSG_REVERB_NONE));
	fReverbClosetMenuItem = new BMenuItem(B_TRANSLATE("Closet"),
		new BMessage(MSG_REVERB_CLOSET));
	fReverbGarageMenuItem = new BMenuItem(B_TRANSLATE("Garage"),
		new BMessage(MSG_REVERB_GARAGE));
	fReverbIgorMenuItem = new BMenuItem(B_TRANSLATE("Igor's lab"),
		new BMessage(MSG_REVERB_IGOR));
	fReverbCavern = new BMenuItem(B_TRANSLATE("Cavern"),
		new BMessage(MSG_REVERB_CAVERN));
	fReverbDungeon = new BMenuItem(B_TRANSLATE("Dungeon"),
		new BMessage(MSG_REVERB_DUNGEON));
 
	reverbPopUpMenu->AddItem(fReverbNoneMenuItem);
	reverbPopUpMenu->AddItem(fReverbClosetMenuItem);
	reverbPopUpMenu->AddItem(fReverbGarageMenuItem);
	reverbPopUpMenu->AddItem(fReverbIgorMenuItem);
	reverbPopUpMenu->AddItem(fReverbCavern);
	reverbPopUpMenu->AddItem(fReverbDungeon);
 
	fReverbMenuField = new BMenuField(B_TRANSLATE("Reverb:"), reverbPopUpMenu);
}
 
 
void
MidiPlayerWindow::CreateViews()
{
	// Set up needed views
	fScopeView = new ScopeView();
 
	fShowScopeCheckBox = new BCheckBox("showScope", B_TRANSLATE("Scope"),
		new BMessage(MSG_SHOW_SCOPE));
	fShowScopeCheckBox->SetValue(B_CONTROL_ON);
 
	CreateInputMenu();
	CreateReverbMenu();
 
	fVolumeSlider = new BSlider("volumeSlider", NULL, NULL, 0, 100,
		B_HORIZONTAL);
	rgb_color color = (rgb_color){ 152, 152, 255 };
	fVolumeSlider->UseFillColor(true, &color);
	fVolumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
	fVolumeSlider->SetExplicitMinSize(
		BSize(fScopeView->Frame().Width(), B_SIZE_UNSET));
 
	fPlayButton = new BButton("playButton", B_TRANSLATE("Play"),
		new BMessage(MSG_PLAY_STOP));
	fPlayButton->SetEnabled(false);
 
	BStringView* volumeLabel = new BStringView(NULL, B_TRANSLATE("Volume:"));
	volumeLabel->SetAlignment(B_ALIGN_LEFT);
 
	// Build the layout
	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
		.Add(fScopeView)
		.AddGroup(B_VERTICAL, 0)
			.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
				.Add(fShowScopeCheckBox, 1, 0)
 
				.Add(fReverbMenuField->CreateLabelLayoutItem(), 0, 1)
				.AddGroup(B_HORIZONTAL, 0.0f, 1, 1)
					.Add(fReverbMenuField->CreateMenuBarLayoutItem())
					.AddGlue()
					.End()
 
				.Add(fInputMenuField->CreateLabelLayoutItem(), 0, 2)
				.AddGroup(B_HORIZONTAL, 0.0f, 1, 2)
					.Add(fInputMenuField->CreateMenuBarLayoutItem())
					.AddGlue()
					.End()
 
				.Add(volumeLabel, 0, 3)
				.Add(fVolumeSlider, 0, 4, 2, 1)
				.End()
			.AddGlue()
			.SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING,
				B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
 
			.End()
		.Add(new BSeparatorView(B_HORIZONTAL))
		.AddGroup(B_VERTICAL, 0)
			.Add(fPlayButton)
			.SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
			.End()
		.End();
}
 
 
void
MidiPlayerWindow::InitControls()
{
	Lock();
 
	fShowScopeCheckBox->SetValue(fScopeEnabled ? B_CONTROL_ON : B_CONTROL_OFF);
	fScopeView->SetEnabled(fScopeEnabled);
 
	fInputOffMenuItem->SetMarked(true);
 
	fReverbNoneMenuItem->SetMarked(fReverbMode == B_REVERB_NONE);
	fReverbClosetMenuItem->SetMarked(fReverbMode == B_REVERB_CLOSET);
	fReverbGarageMenuItem->SetMarked(fReverbMode == B_REVERB_GARAGE);
	fReverbIgorMenuItem->SetMarked(fReverbMode == B_REVERB_BALLROOM);
	fReverbCavern->SetMarked(fReverbMode == B_REVERB_CAVERN);
	fReverbDungeon->SetMarked(fReverbMode == B_REVERB_DUNGEON);
	be_synth->SetReverb(fReverbMode);
 
	fVolumeSlider->SetValue(fVolume);
 
	if (fWindowX != -1 && fWindowY != -1)
		MoveTo(fWindowX, fWindowY);
	else
		CenterOnScreen();
 
	Unlock();
}
 
 
void
MidiPlayerWindow::LoadSettings()
{
	BFile file(SETTINGS_FILE, B_READ_ONLY);
	if (file.InitCheck() != B_OK || file.Lock() != B_OK)
		return;
 
	file.ReadAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
	file.ReadAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
	file.ReadAttr("Volume", B_INT32_TYPE, 0, &fWindowX, sizeof(int32));
	file.ReadAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
	file.ReadAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
 
	file.Unlock();
}
 
 
void
MidiPlayerWindow::SaveSettings()
{
	BFile file(SETTINGS_FILE, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
	if (file.InitCheck() != B_OK || file.Lock() != B_OK)
		return;
 
	file.WriteAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
	file.WriteAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
	file.WriteAttr("Volume", B_INT32_TYPE, 0, &fVolume, sizeof(int32));
	file.WriteAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
	file.WriteAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
 
	file.Sync();
	file.Unlock();
}
 
 
void
MidiPlayerWindow::LoadFile(entry_ref* ref)
{
	if (fIsPlaying) {
		fScopeView->SetPlaying(false);
		fScopeView->Invalidate();
		UpdateIfNeeded();
 
		StopSynth();
	}
 
	fMidiSynthFile.UnloadFile();
 
	if (fMidiSynthFile.LoadFile(ref) == B_OK) {
		// Ideally, we would call SetVolume() in InitControls(),
		// but for some reason that doesn't work: BMidiSynthFile
		// will use the default volume instead. So we do it here.
		fMidiSynthFile.SetVolume(fVolume / 100.0f);
 
		fPlayButton->SetEnabled(true);
		fPlayButton->SetLabel(B_TRANSLATE("Stop"));
		fScopeView->SetHaveFile(true);
		fScopeView->SetPlaying(true);
		fScopeView->Invalidate();
 
		StartSynth();
	} else {
		fPlayButton->SetEnabled(false);
		fPlayButton->SetLabel(B_TRANSLATE("Play"));
		fScopeView->SetHaveFile(false);
		fScopeView->SetPlaying(false);
		fScopeView->Invalidate();
 
		BAlert* alert = new BAlert(NULL, B_TRANSLATE("Could not load song"),
			B_TRANSLATE("OK"), NULL, NULL,
			B_WIDTH_AS_USUAL, B_STOP_ALERT);
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
	}
}
 
 
void
MidiPlayerWindow::StartSynth()
{
	fMidiSynthFile.Start();
	fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
	fIsPlaying = true;
}
 
 
void
MidiPlayerWindow::StopSynth()
{
	if (!fMidiSynthFile.IsFinished())
		fMidiSynthFile.Fade();
 
	fIsPlaying = false;
}
 
 
void
MidiPlayerWindow::_StopHook(int32 arg)
{
	((MidiPlayerWindow*)(addr_t)arg)->StopHook();
}
 
 
void
MidiPlayerWindow::StopHook()
{
	if (Lock()) {
		// we may be called from the synth's thread
 
		fIsPlaying = false;
 
		fScopeView->SetPlaying(false);
		fScopeView->Invalidate();
		fPlayButton->SetEnabled(true);
		fPlayButton->SetLabel(B_TRANSLATE("Play"));
 
		Unlock();
	}
}
 
 
void
MidiPlayerWindow::OnPlayStop()
{
	if (fIsPlaying) {
		fPlayButton->SetEnabled(false);
		fScopeView->SetPlaying(false);
		fScopeView->Invalidate();
		UpdateIfNeeded();
 
		StopSynth();
	} else {
		fPlayButton->SetLabel(B_TRANSLATE("Stop"));
		fScopeView->SetPlaying(true);
		fScopeView->Invalidate();
 
		StartSynth();
	}
}
 
 
void
MidiPlayerWindow::OnShowScope()
{
	fScopeEnabled = !fScopeEnabled;
	fScopeView->SetEnabled(fScopeEnabled);
	fScopeView->Invalidate();
	SaveSettings();
}
 
 
void
MidiPlayerWindow::OnInputChanged(BMessage* message)
{
	int32 newId;
	if (message->FindInt32("id", &newId) == B_OK) {
		BMidiProducer* endpoint = BMidiRoster::FindProducer(fInputId);
		if (endpoint != NULL) {
			endpoint->Disconnect(fSynthBridge);
			endpoint->Release();
		}
 
		fInputId = newId;
 
		endpoint = BMidiRoster::FindProducer(fInputId);
		if (endpoint != NULL) {
			if (!fInstrumentLoaded) {
				fScopeView->SetLoading(true);
				fScopeView->Invalidate();
				UpdateIfNeeded();
 
				fSynthBridge->Init(B_BIG_SYNTH);
				fInstrumentLoaded = true;
 
				fScopeView->SetLoading(false);
				fScopeView->Invalidate();
			}
 
			endpoint->Connect(fSynthBridge);
			endpoint->Release();
 
			fScopeView->SetLiveInput(true);
			fScopeView->Invalidate();
		} else {
			fScopeView->SetLiveInput(false);
			fScopeView->Invalidate();
		}
	}
}
 
 
void
MidiPlayerWindow::OnReverb(reverb_mode mode)
{
	fReverbMode = mode;
	be_synth->SetReverb(fReverbMode);
	SaveSettings();
}
 
 
void
MidiPlayerWindow::OnVolume()
{
	fVolume = fVolumeSlider->Value();
	fMidiSynthFile.SetVolume(fVolume / 100.0f);
	SaveSettings();
}
 
 
void
MidiPlayerWindow::OnDrop(BMessage* message)
{
	entry_ref ref;
	if (message->FindRef("refs", &ref) == B_OK)
		LoadFile(&ref);
}

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.