//---------------------------------------------------------------------------
//	Video editor
//	2004 - 2007 Stanislav Sumec <sumec@fit.vutbr.cz>
//	Brno University of Technology
//	Faculty of Information Technology
//---------------------------------------------------------------------------
char *Copyright = "Copyright (c) 2004 - 2007 Stanislav Sumec <sumec@fit.vutbr.cz>";
//---------------------------------------------------------------------------
#ifdef __BORLANDC__
#include <system.hpp>
#include <StrUtils.hpp>
#include <time.h>
#endif
#include <libxml/tree.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#pragma hdrstop
#include "Editor.h"
#include "Img.h"
#include "Error.h"
#include "FSTools.h"
#include "avfile_utils.h"

#include "EffectBasic.h"
#include "EffectZoom.h"
#include "EffectFade.h"
#include "EffectPIP.h"
#include "EffectMix.h"
#include "EffectStack.h"
#include "EffectHMirror.h"
#include "EffectAudioBasic.h"
#include "EffectAudioFade.h"
#include "EffectPerspective.h"
#include "EffectCrop.h"
#ifdef _WIN32
#include "EffectText.h"
#endif

#include "camfile.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)

//---------------------------------------------------------------------------
//using namespace ee;

//---------------------------------------------------------------------------
TEditor::TEditor()
{
	TEffect::SetupVideoStack(&VideoStack);
#ifndef VIDEO_EDITOR_LIB
	TEffect *Effect;
	Effect = new TEffectBasic();
	Effects.Add(Effect);
	Effect = new TEffectZoom();
	Effects.Add(Effect);
	Effect = new TEffectFade();
	Effects.Add(Effect);
	Effect = new TEffectPIP();
	Effects.Add(Effect);
	Effect = new TEffectMix();
	Effects.Add(Effect);
	Effect = new TEffectStack();
	Effects.Add(Effect);
	Effect = new TEffectHMirror();
	Effects.Add(Effect);
	Effect = new TEffectPerspective();
	Effects.Add(Effect);
	Effect = new TEffectCrop();
	Effects.Add(Effect);
#ifdef _WIN32
	Effect = new TEffectText();
	Effects.Add(Effect);
#endif
	Effect = new TEffectAudioFade();
	AudioEffects.Add(Effect);
#else
	std::cout << Copyright << std::endl;
#endif
	LoadedFileName = "";
}

//---------------------------------------------------------------------------
TEditor::~TEditor()
{
	Sources.clear();
	FreeGraphs();
	VideoStack.Clear();
	ImpExp.Clear();
}

//---------------------------------------------------------------------------
void TEditor::AddVideoEffect(TEffect *Effect)
{
	Effects.Add(Effect);
}

//---------------------------------------------------------------------------
void TEditor::AddAudioEffect(TEffect *Effect)
{
	AudioEffects.Add(Effect);
}

//---------------------------------------------------------------------------
void TEditor::FreeGraphs()
{
	TPEffects::iterator i;
	i = Graph.begin();
	while (i != Graph.end())
	{
		delete (*i);
		i++;
	}
	Graph.clear();
	i = AudioGraph.begin();
	while (i != AudioGraph.end())
	{
		delete (*i);
		i++;
	}
	AudioGraph.clear();
}

//---------------------------------------------------------------------------
void TEditor::Load(const char *FileName, const char *MediaPath)
{
	Sources.clear();
	OutputID = -1;

	ImpExp.Clear();
	ImpExp.Load(FileName, false, false, -1);
	if (AnsiString("*HUB*").AnsiCompare(AnsiString(FileName).SubString(0, 5)) == 0)
	{
		memcpy((void*)FileName, FileName+5, strlen(FileName));
//		FileName += 5;
	}
	LoadedFileName = FileName;
	for (TImpExp::TSources::iterator i = ImpExp.Sources.begin(); i != ImpExp.Sources.end(); i++)
	{
		std::string SourceFileName = i->FileName;
		TExtendedSource *Source = new TExtendedSource();

		Source->Camera = i->Camera;
		Source->Offset = i->Offset;
		Source->Loop = i->Loop;

		// uplna cesta
		//int OpenType = TAVFile::otVideo/*Audio*/;
		int OpenType = TAVFile::otVideoAudio;
		Source->AVFile = AVFileOpen(NormalizeFilePath(SourceFileName).c_str(), OpenType);
		// relativni cesta vzhledem k xml
		if (!Source->AVFile)
		{
			Source->AVFile = AVFileOpen((NormalizeFilePath(ExtractFilePath(std::string(FileName)) + SourceFileName)).c_str(), OpenType);
		}
		// prohledavani zadanych adresaru
		if (!Source->AVFile && MediaPath)
		{
			Source->AVFile = AVFileOpen((NormalizeFilePath(std::string(MediaPath) + PATH_DELIMINER + SourceFileName)).c_str(), OpenType);
		}

		Source->FileName = NormalizeFilePath(ExtractFilePath(std::string(FileName)) + SourceFileName);
		// soubor nenalezen
		if (!Source->AVFile)
		{
//!!!//		Source->AVFile = AVFileCreate("");
			Source->AVFile = new TCAMFile(Source->Camera);
//			((TCAMFile*)(Source->AVFile))->InitCamera(Source->Camera);
			Source->AVFile->Open(Source->FileName.c_str(), TAVFile::omRead, TAVFile::otVideo, true);
			std::cerr << "File not opened: " << SourceFileName << std::endl;
		}

		TAVFile::TParameters Parameters = Source->AVFile->GetParameters();
		Parameters.Deinterlace = true;
		Source->AVFile->SetParameters(Parameters);

		Sources.push_back(Source);
	}

	OutputID = ImpExp.GetDefaultMessageID(TImpExp::detOutput);
	UpdateSetup();
}

//---------------------------------------------------------------------------
void TEditor::UpdateSetup()
{
	SetupInputAspectRatio = SetupCropAspectRatio = 0;

	int ID = ImpExp.GetDefaultMessageID(TImpExp::detSetup);
	for (ee::TEvents::iterator i = ImpExp.Events.begin(); i != ImpExp.Events.end(); i++)
	{
		ee::TEvent *m = *i;
		if (m->ID == ID)
		{
			if (!ee::GetParameterFloat(m->Parameters, "InputAspectRatio", SetupInputAspectRatio))
			{
				SetupInputAspectRatio = 0;
			}
			if (!ee::GetParameterFloat(m->Parameters, "CropAspectRatio", SetupCropAspectRatio))
			{
				SetupCropAspectRatio = 0;
			}
		}
	}
}

//---------------------------------------------------------------------------
void TEditor::SetupQuality(int Value)
{
	TEffect::SetupQuality(Value);
	TImg::SetupQuality(Value);
}

//---------------------------------------------------------------------------
void TEditor::Save(const char *FileName, TSaveOptions Options, void (*Progress)(int Pos, int Max))
{
	TAVFile::TFrameInfo FI, DstFI;
	TAVFile::TAudioInfo AI;
	FI.Width = 0;
	FI.Height = 0;
	FI.FrameRate.Rate = 0;
	FI.FrameRate.Scale = 1;
	int Length = 0;
	bool VSExists = false;
	int AudioSource = -1;

	for (unsigned int i = 0; i < Sources.Sources.size(); i++)
	{
		TExtendedSource *Source = Sources.Sources[i];
		if (Source->AVFile && (Source->AVFile->GetOpenMode() == TAVFile::omRead) && (Source->AVFile->GetOpenType() & TAVFile::otVideo))
		{
			TAVFile::TFrameInfo SrcFI = Source->AVFile->GetFrameInfo();

			if (SrcFI.Width > FI.Width)
			{
				FI.Width = SrcFI.Width;
			}
			if (SrcFI.Height > FI.Height)
			{
				FI.Height = SrcFI.Height;
			}

			if (FI.FrameRate < SrcFI.FrameRate)
			{
				FI.FrameRate = SrcFI.FrameRate;
			}

			int l = TAVFile::Round((1000.0 * double(SrcFI.FrameRate.Scale) * double(Sources[i]->AVFile->GetVideoLength())) / double(SrcFI.FrameRate.Rate)) + Sources[i]->Offset;
			if (l > Length)
			{
				Length = l;
			}

			VSExists = true;
		}

		if (Options.Audio && Source->AVFile && (Source->AVFile->GetOpenMode() == TAVFile::omRead) && (Source->AVFile->GetOpenType() & TAVFile::otAudio))
		{
			if (AudioSource < 0)
			{
				AI = Source->AVFile->GetAudioInfo();
			}
			AudioSource = i;
		}
	}

	if (!VSExists)
	{
		throw TError("No video source available!");
	}

	if (Options.Width > 0 && Options.Height > 0)
	{
		FI.Width = Options.Width;
		FI.Height = Options.Height;
	}

	if (SetupInputAspectRatio > 0)
	{
		FI.Width = TAVFile::Round(SetupInputAspectRatio * FI.Height);
	}

/*
	switch (ResampleSources)
	{
	case 1:
		FI.Width /= 2;
		FI.Height /= 2;
		break;
	case 2:
		FI.Width /= 4;
		FI.Height /= 4;
		break;
	}
*/

	TAVFile *Output = AVFileCreate(FileName);

	DstFI = FI;
	if (SetupCropAspectRatio > 0)
	{
		DstFI.Width = TAVFile::Round(SetupCropAspectRatio * DstFI.Height);
	}

	TAVFile::TFrameInfo OutputFI = DstFI;
	if (Options.Width > 0 && Options.Height > 0)
	{
		OutputFI.Width = Options.Width;
		OutputFI.Height = Options.Height;
	}

	try
	{
		TAVFile::EOpenType Type;
		if (Options.Video)
		{
			Type = TAVFile::otVideo;
		}
		else
		{
			Type = TAVFile::otNone;
		}
		Output->SetFrameInfo(OutputFI);

		TAVFile::TParameters Parameters = Output->GetParameters();
		if (Options.VideoBitRate > 0)
		{
			Parameters.BitRate = Options.VideoBitRate;
		}
		if (Options.AudioBitRate > 0)
		{
			Parameters.AudioBitRate = Options.AudioBitRate;
		}
		if (Options.VideoCodec != "")
		{
			Parameters.Codec = Options.VideoCodec;
		}
		if (Options.AudioCodec != "")
		{
			Parameters.AudioCodec = Options.AudioCodec;
		}
		Output->SetParameters(Parameters);

		if (AudioSource >= 0)
		{
			Output->SetAudioInfo(AI);
			Type = TAVFile::EOpenType(Type + TAVFile::otAudio);
		}
		else
		{
			Options.Audio = false;
		}
		Output->Open(FileName, TAVFile::omWrite, Type);
	}
	catch (...)
	{
		delete Output;
		throw;
	}

//	Sources.Init();
	Sources.Init(0, TExtendedSources::EResize(Options.ResampleSources));
	TEffect::SetupQuality(Options.Quality);
	TImg::SetupQuality(Options.Quality);

	TImg *Frame, *DstFrame, *OutputFrame;
	TWave *Wave;
	Frame = DstFrame = OutputFrame = NULL;
	Wave = NULL;
	try
	{
		int Step = TAVFile::Round((1000.0 * double(FI.FrameRate.Scale)) / double(FI.FrameRate.Rate));
		int Position = 0;
		Length = std::min(Options.Start + Options.MaxLength, Length);

		if (Options.Video)
		{
			TEffect::SetupDefaultSize(FI.Width, FI.Height);
			Frame = new TImg(FI.Width, FI.Height);
			DstFrame = new TImg(DstFI.Width, DstFI.Height);
			OutputFrame = new TImg(OutputFI.Width, OutputFI.Height);
		}
		if (Options.Audio)
		{
			Wave = new TWave(AI.Channels, AI.SamplesPerSec);
		}

		ee::TEvents::iterator SItem = ImpExp.Events.begin();
		int Camera = -1;
		ee::TParameters Parameters;
		SetParameters(Parameters);
		TEffect MainEffect;

		while (Position < Length)
		{
			if (Progress)
			{
				Progress(Position, Length);
			}

			while (SItem != ImpExp.Events.end() && (*SItem)->Time <= Position)
			{
				if ((*SItem)->ID == OutputID)
				{
					ee::TParameters Parameters = (*SItem)->Parameters;
					if (!ee::GetParameterInt(Parameters, "Camera", Camera))
					{
						Camera = -1;
					}
/*
					if (Options.Type & 1)
					{
						Parameters.push_back(TParameter("Effect", "Zoom1000"));
						Parameters.push_back(TParameter("Zoom1000Ratio", "0,666666"));
						Parameters.push_back(TParameter("Zoom1000CenterX", "50"));
						Parameters.push_back(TParameter("Zoom1000CenterY", "75"));
//						Parameters.push_back(TParameter("Zoom1000CenterY", "33,333333"));

						Parameters.push_back(TParameter("Effect", "PIP1000"));
						Parameters.push_back(TParameter("PIP1000Camera", "1"));
						Parameters.push_back(TParameter("PIP1000Ratio", "0,333333"));
						Parameters.push_back(TParameter("PIP1000CenterX", "16,666666"));
						Parameters.push_back(TParameter("PIP1000CenterY", "83,333333"));

						Parameters.push_back(TParameter("Effect", "PIP1001"));
						Parameters.push_back(TParameter("PIP1001Camera", "3"));
						Parameters.push_back(TParameter("PIP1001Ratio", "0,333333"));
						Parameters.push_back(TParameter("PIP1001CenterX", "50"));
						Parameters.push_back(TParameter("PIP1001CenterY", "83,333333"));

						Parameters.push_back(TParameter("Effect", "PIP1002"));
						Parameters.push_back(TParameter("PIP1002Camera", "2"));
						Parameters.push_back(TParameter("PIP1002Ratio", "0,333333"));
						Parameters.push_back(TParameter("PIP1002CenterX", "83,333333"));
						Parameters.push_back(TParameter("PIP1002CenterY", "83,333333"));
					}
*/
					SetParameters(Parameters);
				}
				SItem++;
			}

			Sources.SetTime(Position);
			if (Position >= Options.Start && (Camera >= 0 || Options.Type & 2))
			{
				Process(Frame, Wave);

				if (Frame)
				{
					if (DstFI == FI && FI.Width == Frame->GetWidth() && FI.Height == Frame->GetHeight())
					{
						if (OutputFI == DstFI)
						{
							Output->PutFrame(Frame->GetBitmap());
						}
						else
						{
							OutputFrame->ResizeFrom(*Frame);
							Output->PutFrame(OutputFrame->GetBitmap());
						}
					}
					else
					{
						DstFrame->CropFrom(*Frame);
						if (OutputFI == DstFI)
						{
							Output->PutFrame(DstFrame->GetBitmap());
						}
						else
						{
							OutputFrame->ResizeFrom(*DstFrame);
							Output->PutFrame(OutputFrame->GetBitmap());
						}
					}
				}

				if (Wave)
				{
					Output->PutAudio(Wave->Wave, Wave->Size);
				}
			}
			else
			{
				Sources.SkipFrame();
			}

			Position += Step;
			if (Progress)
			{
				Progress(Position, Length);
			}
		}

		if (Frame)
		{
			delete Frame;
		}
		if (DstFrame)
		{
			delete DstFrame;
		}
		if (OutputFrame)
		{
			delete OutputFrame;
		}
		if (Wave)
		{
			delete Wave;
		}
		delete Output;
	}
	catch (...)
	{
		if (Frame)
		{
			delete Frame;
		}
		if (DstFrame)
		{
			delete DstFrame;
		}
		if (OutputFrame)
		{
			delete OutputFrame;
		}
		if (Wave)
		{
			delete Wave;
		}
		delete Output;
		throw;
	}
}

//---------------------------------------------------------------------------
void TEditor::Process(TImg *Frame, TWave *Wave)
{
	// video processing
	if (Frame)
	{
		int BaseWidth = Frame->GetWidth();
		int BaseHeight = Frame->GetHeight();
		for (TPEffects::iterator i = Graph.begin(); i != Graph.end(); i++)
		{
			(*i)->DoVideoA(Sources, *Frame, (i + 1) == Graph.end() || (*(i + 1))->GetNeedFrame(), BaseWidth, BaseHeight);
			if ((i + 1) == Graph.end() || (*(i + 1))->GetNeedFullFrame())
			{
				int Width, Height, ViewWidth, ViewHeight;
				Frame->GetSize(Width, Height);
				Frame->GetViewSize(ViewWidth, ViewHeight);
				if (Width != ViewWidth || Height != ViewHeight)
				{
					if (BaseWidth != ViewWidth || BaseHeight != ViewHeight)
					{
						TImg TMP(BaseWidth, BaseHeight);
						TMP.CropResizeFrom(*Frame, ViewWidth, ViewHeight);
						TMP.MoveTo(*Frame);
					}
					else
					{
						TImg TMP(ViewWidth, ViewHeight);
						TMP.ResizeFrom(*Frame);
						TMP.MoveTo(*Frame);
					}
				}
				else if (BaseWidth != ViewWidth || BaseHeight != ViewHeight)
				{
					TImg TMP(BaseWidth, BaseHeight);
					TMP.CropFrom(*Frame);
					TMP.MoveTo(*Frame);
				}
				/*
				if (Width != ViewWidth || Height != ViewHeight)
				{
					TImg TMP(ViewWidth, ViewHeight);
					TMP.ResizeFrom(*Frame);
					TMP.MoveTo(*Frame);
				}
				if (BaseWidth != ViewWidth || BaseHeight != ViewHeight)
				{
					TImg TMP(BaseWidth, BaseHeight);
					TMP.CropFrom(*Frame);
					TMP.MoveTo(*Frame);
				}
				*/
			}
		}
	}

	// audio processing
	if (Wave)
	{
		for (TPEffects::iterator i = AudioGraph.begin(); i != AudioGraph.end(); i++)
		{
			(*i)->DoAudio(Sources, *Wave);
		}
	}
}

//---------------------------------------------------------------------------
void TEditor::SetParameters(ee::TParameters& Parameters)
{
	TEffect *Effect;
	FreeGraphs();

	// video graph
	Effect = new TEffectBasic();
	Effect->SetupVideo(Parameters, NULL);
	Graph.push_back(Effect);
	for (ee::TParameters::iterator p = Parameters.begin(); p != Parameters.end(); p++)
	{
		if (AnsiStartsText("Effect", p->Name) && (Effect = Effects.Get(p->Value.c_str())) != NULL)
		{
			if (Effect->SetupVideo(Parameters, (char*)p->Value.c_str()))
			{
				Graph.push_back(Effect);
			}
			else
			{
				delete Effect;
			}
		}
	}

	// audio graph
	Effect = new TEffectAudioBasic();
	Effect->SetupAudio(Parameters, NULL);
	AudioGraph.push_back(Effect);
	for (ee::TParameters::iterator p = Parameters.begin(); p != Parameters.end(); p++)
	{
		if (AnsiStartsText("AudioEffect", p->Name) && (Effect = Effects.Get(p->Value.c_str())) != NULL)
		{
			if (Effect->SetupAudio(Parameters, (char*)p->Value.c_str()))
			{
				AudioGraph.push_back(Effect);
			}
			else
			{
				delete Effect;
			}
		}
	}
}

//---------------------------------------------------------------------------
TEditor::TSaveOptions::TSaveOptions()
{
	Start = 0;
	MaxLength = INT_MAX;
	Video = Audio = true;
	Type = 0;
	ResampleSources = 0;
	Quality = 0;
	AudioBitRate = VideoBitRate = -1;
	AudioCodec = VideoCodec = "";
	Width = Height = -1;
}

