/////////////////////////////////////////////////////////////////////////////
// Name:        VobListBox.cpp
// Purpose:     The list box to display information about streams in given VOB
// Author:      Alex Thuering
// Created:     03.05.2009
// RCS-ID:      $Id: VobListBox.cpp,v 1.10 2010/04/10 09:22:58 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "VobListBox.h"
#include "Config.h"
#include "SubtitlePropDlg.h"
#include <wx/filedlg.h>
#include <wx/arrimpl.cpp>
#include <wxSVG/mediadec_ffmpeg.h>
#include <wxVillaLib/utils.h>
#include <wxVillaLib/rc/video.png.h>
#include <wxVillaLib/rc/audio.png.h>
#include <wxVillaLib/rc/subtitle.png.h>

WX_DEFINE_OBJARRAY(RectList);
WX_DEFINE_OBJARRAY(RectListOfList);
WX_DEFINE_OBJARRAY(StringListOfList);

BEGIN_EVENT_TABLE(VobListBox, wxVListBox)
	EVT_LEFT_DCLICK(VobListBox::OnDoubleClick)
END_EVENT_TABLE()

const int ITEM_HEIGHT = 50;
#define ITEM_FONT wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)

VobListBox::VobListBox(wxWindow* parent, wxWindowID id, Vob* vob, wxArrayString audioLangCodes,
		wxArrayString subtitleLangCodes, DVD* dvd): wxVListBox(parent, id) {
	m_vob = vob;
	m_audioLangCodes = audioLangCodes;
	m_subtitleLangCodes = subtitleLangCodes;
	m_dvd = dvd;
	m_videoImg = Scale(LoadFrame(m_vob->GetFilename()));
	m_audioImg = Scale(wxBITMAP_FROM_MEMORY(audio).ConvertToImage());
	m_subtitleImg = Scale(wxBITMAP_FROM_MEMORY(subtitle).ConvertToImage());
	RefreshInfo();
	SetMinSize(wxSize(-1, 300));
}

wxCoord VobListBox::OnMeasureItem(size_t n) const {
	if (n >= m_infoRect.size())
		return ITEM_HEIGHT;
	int h = m_infoRect[n][m_infoRect[n].size()-1].GetBottom();
	return h + 4 > ITEM_HEIGHT ? h + 4 : ITEM_HEIGHT;
}

void VobListBox::OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const {
	// image
	wxBitmap image = n == 0 ? m_videoImg : n > m_vob->GetAudioFilenames().size() ? m_subtitleImg : m_audioImg;
	dc.DrawBitmap(image, rect.x + 2, rect.y + 2);
	
	// info
	dc.SetFont(ITEM_FONT);
	dc.SetTextForeground((int)n == GetSelection() ? *wxWHITE : *wxBLACK);
	wxArrayString& info = m_info[n];
	RectList& infoRect = m_infoRect[n];
	for (unsigned int i=0; i<info.GetCount(); i++) {
		dc.DrawText(info[i], rect.x + infoRect[i].GetX(), rect.y + infoRect[i].GetY());
	}
}

void VobListBox::RefreshInfo() {
	SetItemCount(1 + m_vob->GetAudioFilenames().size() + m_vob->GetSubtitles().size());
	
	m_info.Clear();
	m_infoRect.Clear();
	
	int choiceIdx = 0;
	int itemY = 0;
	unsigned int audioLangIdx = 0;
	for (unsigned int n = 0; n < 1 + m_vob->GetAudioFilenames().size(); n++) {
		m_info.Add(wxArrayString());
		m_infoRect.Add(RectList());
		
		wxMemoryDC dc;
		dc.SetFont(ITEM_FONT);
		wxBitmap image = n == 0 ? m_videoImg : m_audioImg;
		int x = image.GetWidth() + 8;
		int y = 2;
		
		// filename
		y = AddInfo(n == 0 ? m_vob->GetFilename() : m_vob->GetAudioFilenames()[n-1], n, dc, x, y);
			
		// duration
		if (n == 0) {
			wxString s = _("Duration:") + wxString(wxT(" "));
			if (m_vob->GetDuration()>0) {
				int secs = (int) m_vob->GetDuration();
				int mins = secs / 60;
				secs %= 60;
				int hours = mins / 60;
				mins %= 60;
				s += wxString::Format(wxT("%02d:%02d:%02d"), hours, mins, secs);
			} else
				s += wxT("N/A");
			y = AddInfo(s, n, dc, x, y);
		}
		
		// stream info
		int stIdx = n == 0 ? 0 : (int)m_vob->GetStreams().GetCount() - m_vob->GetAudioFilenames().GetCount() + n - 1;
		int streamsCount = n == 0 ? (int)m_vob->GetStreams().GetCount() - m_vob->GetAudioFilenames().GetCount() : 1;
		for (int stN = 0; stN < streamsCount; stN++) {
			Stream* stream = m_vob->GetStreams()[stIdx + stN];
			wxString srcFormat = stream->GetSourceFormat();
			switch (stream->GetType()) {
			case stVIDEO: {
				y = AddInfo(_("Video:") + wxString(wxT(" ")) + srcFormat, n, dc, x, y);
				wxRect& rect = m_infoRect[n][m_infoRect[n].size()-1];
				y += AddChoiceCtrl(DVD::GetVideoFormatLabels(true), stream->GetVideoFormat() - 1, rect, itemY,
						choiceIdx, !m_vob->GetDoNotTranscode());
				break;
			}
			case stAUDIO: {
				y = AddInfo(_("Audio:") + wxString(wxT(" ")) + srcFormat, n, dc, x, y);
				wxRect& rect = m_infoRect[n][m_infoRect[n].size()-1];
				wxRect rect2 = rect; // copy before it will be changed
				y += AddChoiceCtrl(DVD::GetAudioFormatLabels(true, true), stream->GetAudioFormat(), rect, itemY,
						choiceIdx, !m_vob->GetDoNotTranscode());
				rect2.x += m_choiceList[choiceIdx-1]->GetSize().GetWidth() + 2;
				int langIdx = DVD::GetAudioLanguageCodes().Index(audioLangIdx < m_audioLangCodes.GetCount()
						? m_audioLangCodes[audioLangIdx] : s_config.GetDefAudioLanguage());
				audioLangIdx++;
				AddChoiceCtrl(DVD::GetAudioLanguageCodes(), langIdx, rect2, itemY, choiceIdx, true);
				break;
			}
			case stSUBTITLE:
				y = AddInfo(_("Subtitle:") + wxString(wxT(" ")) + srcFormat, n, dc, x, y);
				break;
			default:
				break;
			}
		}
		itemY += y > ITEM_HEIGHT ? y + 1 : ITEM_HEIGHT + 1;
	}

	m_subtitleChoiceIdx = m_choiceList.size();
	for (unsigned int si = 0; si < m_vob->GetSubtitles().size(); si++) {
		m_info.Add(wxArrayString());
		m_infoRect.Add(RectList());
		int n = m_info.GetCount() - 1;
		
		wxMemoryDC dc;
		dc.SetFont(ITEM_FONT);
		int x = m_subtitleImg.GetWidth() + 8;
		int y = 2;
		
		// filename
		y = AddInfo(m_vob->GetSubtitles()[si]->GetFilename(), n, dc, x, y);
		y = AddInfo(_("Subtitle:") + wxString(wxT(" ")) + m_vob->GetSubtitles()[si]->GetFilename().AfterLast(wxT('.')),
				n, dc, x, y);

		int langIdx = DVD::GetAudioLanguageCodes().Index(si < m_subtitleLangCodes.GetCount()
				? m_subtitleLangCodes[si] : s_config.GetDefSubtitleLanguage());
		wxRect& rect = m_infoRect[n][m_infoRect[n].size()-1];
		y += AddChoiceCtrl(DVD::GetAudioLanguageCodes(), langIdx, rect, itemY, choiceIdx, true);
		itemY += y > ITEM_HEIGHT ? y + 1 : ITEM_HEIGHT + 1;
	}
}

int VobListBox::AddInfo(const wxString& s, int n, wxDC& dc, int x, int y) {
	m_info[n].Add(s);
	wxCoord w;
	wxCoord h;
	dc.GetTextExtent(s, &w, &h);
	m_infoRect[n].Add(wxRect(x, y, w, h));
	return y + h + 2;
}

int VobListBox::AddChoiceCtrl(wxArrayString formats, int selection, wxRect& rect, int itemY, int& choiceIdx,
		bool enabled) {
	wxChoice* ctrl = NULL;
	if (choiceIdx >= (int) m_choiceList.size()) {
		ctrl = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, formats);
		m_choiceList.Add(ctrl);
		ctrl->SetSelection(selection);
	} else
		ctrl = m_choiceList[choiceIdx];
	choiceIdx++;
	ctrl->Enable(enabled);
	ctrl->SetPosition(wxPoint(rect.GetRight() + 4, itemY + rect.GetTop()));
	if (ctrl->GetSize().GetHeight() > rect.GetHeight()) {
		int p = (ctrl->GetSize().GetHeight() - rect.GetHeight() + 1)/2;
		rect.SetY(rect.GetY() + p);
		rect.SetHeight(rect.GetHeight() + p);
		return 2*p;
	}
	return 0;
}

wxImage VobListBox::LoadFrame(const wxString& filename) const {
	wxImage image;
	wxFfmpegMediaDecoder decoder;
	if (decoder.Load(filename)) {
		double duration = decoder.GetDuration();
		if (duration > 0) {
			double dpos = duration * 0.05;
			decoder.SetPosition(dpos);
			for (int i = 0; i < 100; i++) {
				image = decoder.GetNextFrame();
				double pos = decoder.GetPosition();
				if (pos >= dpos || pos < 0)
					break;
			}
		} else
			image = decoder.GetNextFrame();
		decoder.Close();
	}
	return image.Ok() ? image : wxBITMAP_FROM_MEMORY(video).ConvertToImage();
}

wxBitmap VobListBox::Scale(wxImage image) {
	int h = ITEM_HEIGHT - 4;
	int w = image.GetWidth()*h/image.GetHeight();
	return wxBitmap(image.Scale(w, h));
}

void VobListBox::RemoveItem(int index) {
	if (index > (int)m_vob->GetAudioFilenames().size()) {
		// remove subtitle
		int subtitleIdx = index - 1 - m_vob->GetAudioFilenames().size();
		m_vob->GetSubtitles().RemoveAt(subtitleIdx);
		m_subtitleLangCodes.RemoveAt(subtitleIdx);
		// choice index = count of video stream + count of audio streams * 2 + count of audio subtitle streams
		int choiceIdx = 1 + m_vob->GetAudioStreamCount()*2 + subtitleIdx;
		m_choiceList[choiceIdx]->Hide();
		m_choiceList.RemoveAt(choiceIdx);
	} else {
		// removed audio file
		m_vob->RemoveAudioFile(index-1);
		// choice index = count of video stream + count of audio streams * 2
		int choiceIdx = 1 + (m_vob->GetAudioStreamCount() - m_vob->GetAudioFilenames().size() + index - 1)*2;
		m_choiceList[choiceIdx]->Hide();
		m_choiceList[choiceIdx + 1]->Hide();
		m_choiceList.RemoveAt(choiceIdx);
		m_choiceList.RemoveAt(choiceIdx);
	}
	RefreshInfo();
	RefreshAll();
}
	

void VobListBox::AddAudio(wxString filename) {
	m_vob->AddAudioFile(filename);
	m_vob->SetDoNotTranscode(false);
	// check if reencoding is needed
	Stream* stream = m_vob->GetStreams()[m_vob->GetStreams().GetCount() - 1];
	if (m_vob->GetStreams().GetCount() == 2 && stream->GetSourceAudioFormat() != m_dvd->GetAudioFormat())
		stream->SetDestinationFormat(m_dvd->GetAudioFormat());
	else if (stream->GetSourceAudioFormat() != afMP2 && stream->GetSourceAudioFormat() != afAC3)
		stream->SetDestinationFormat(m_dvd->GetAudioFormat());
	else if (stream->GetSourceSampleRate() != 48000)
		stream->SetDestinationFormat(stream->GetSourceAudioFormat());
	// update list box
	RefreshInfo();
	RefreshAll();
}

void VobListBox::AddSubtitle(wxString filename) {
	m_vob->GetSubtitles().Add(new TextSub(filename));
	m_subtitleLangCodes.Add(s_config.GetDefSubtitleLanguage());
	// update list box
	RefreshInfo();
	RefreshAll();
}

void VobListBox::SetDoNotTranscode(bool value) {
	m_vob->SetDoNotTranscode(value);
	int choiceIdx = 0;
	for (unsigned int stIdx = 0; stIdx < m_vob->GetStreams().size(); stIdx++) {
		Stream* stream = m_vob->GetStreams()[stIdx];
		switch (stream->GetType()) {
		case stVIDEO:
			if (value)
				m_choiceList[choiceIdx]->SetSelection(vfCOPY-1); // vfNONE is not in the selection list
			m_choiceList[choiceIdx++]->Enable(!value);
			break;
		case stAUDIO:
			if (value)
				m_choiceList[choiceIdx]->SetSelection(afCOPY);
			m_choiceList[choiceIdx++]->Enable(!value);
			choiceIdx++;
			break;
		default:
			break;
		}
	}
}

bool VobListBox::HasAudioFiles() {
	return m_vob->GetAudioFilenames().size() > 0;
}

Vob* VobListBox::UpdateVob() {
	unsigned int choiceIdx = 0;
	unsigned int audioLangIdx = 0;
	for (unsigned int stIdx = 0; stIdx < m_vob->GetStreams().size(); stIdx++) {
		Stream* stream = m_vob->GetStreams()[stIdx];
		switch (stream->GetType()) {
		case stVIDEO:
			stream->SetDestinationFormat(m_choiceList[choiceIdx++]->GetSelection() + 1);
			break;
		case stAUDIO:
			stream->SetDestinationFormat(m_choiceList[choiceIdx++]->GetSelection());
			if (audioLangIdx < m_audioLangCodes.GetCount())
				m_audioLangCodes.RemoveAt(audioLangIdx);
			m_audioLangCodes.Insert(m_choiceList[choiceIdx++]->GetStringSelection(), audioLangIdx++);
			break;
		default:
			break;
		}
	}
	m_subtitleLangCodes.clear();
	for (unsigned int si = 0; si < m_vob->GetSubtitles().size(); si++) {
		m_subtitleLangCodes.Add(m_choiceList[choiceIdx++]->GetStringSelection());
	}
	return m_vob;
}

/**
 * Processes a double click event
 */
void VobListBox::OnDoubleClick(wxMouseEvent& evt) {
	if (GetSelection() >= 1 + (int) m_vob->GetAudioFilenames().GetCount()) {
		UpdateVob();
		int idx = GetSelection() - 1 - m_vob->GetAudioFilenames().GetCount();
		TextSub* textsub = m_vob->GetSubtitles()[idx];
		SubtitlePropDlg dialog(this, textsub, &m_subtitleLangCodes[idx]);
		if (dialog.ShowModal() == wxID_OK) {
			// update subtitle language choice
			// TODO use m_subtitleChoiceIdx
		}
	}
}
