//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4		$Date: 2006/11/13 09:08:27 $
//
// Category     : VST 2.x SDK Samples
// Filename     : AEcho_Hiding_Wtm.cpp
// Created by   : Steinberg Media Technologies
// Description  : Simple Delay plugin (Mono->Stereo)
//
// © 2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------
// Date: 2008/05/10 
//
// Category     : VST 2.x SDK Samples
// Filename     : AEcho_Hiding_Wtm.cpp
// Created by   : David Henzl
// Description  : Audio Watermarking(Echo Hiding) (Mono->Stereo)
//-------------------------------------------------------------------------------------------------------

#include <stdio.h>
#include <string.h>
#include <vstcontrols.h>
#include <complex>
#include <math.h>

#ifndef __AEcho_Hiding_Wtm__
#include "echo_hiding_wtm.h"
#endif

#ifndef __Editor__
#include "../editor/my_editor.h"
#endif
//----------------------------------------------------------------------------- 
AEchoHidingProgram::AEchoHidingProgram ()
{ 
	// default Program Values
	params[AEcho_Hiding_Wtm::kDelay0] = 128.0f;
	params[AEcho_Hiding_Wtm::kDelay1] = 256.0f;
	params[AEcho_Hiding_Wtm::kAlfa] = 0.5f;
	params[AEcho_Hiding_Wtm::kMode] = 0.0f;
	params[AEcho_Hiding_Wtm::kVSTblok] = 0.0f;
	params[AEcho_Hiding_Wtm::kWtmblok] = 0.0f;
	params[AEcho_Hiding_Wtm::kWitchBlok] = 0.0f;
	params[AEcho_Hiding_Wtm::kWatermark] = 0.0f;
	strcpy (name, "Init");
}
//-----------------------------------------------------------------------------
AEcho_Hiding_Wtm::AEcho_Hiding_Wtm (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, kNumPrograms, kNumParams)
{
	// init
	size = 44100;
	Wtmlength=10;//pevna delka vodoznaku
	stepDec=(Wtmlength+1)*8;
	stepSum=200;
	stepRecBits=0;//max. 10*8bitu
	stepSingleWtm=0;//max. 250 vodoznaku
	pSize = 882;  //Info pro WaveDisplay
	fftsize = 2048;
	cursor = 0;
	rcursor = 0;
	wcursor = 0;
	blockCounter = 0;
	BitsCounter  = 0;
	syncposition=0;
	StartDec = 0;
	StopDec = 0;

	L1=0;L2=0;L3=0;
	delay0=0;
	delay1=0;
	Energy=0.0f;
	N=0;
	N2=0;
	ZeroCross=0.0f;
	canWatermark = false;
	canDetect = false;
	sync = false;
	partWtm=0;
	//delay = 100;
	VolumeUnit = 1;
	bufferL = new float[size];
	bufferR = new float[size];
	SumWtm = new int[(Wtmlength+1)*8];
	one = new int[20000];
	zer = new int[20000];
	fftbuffer = new float[fftsize];
	ifftbuffer = new float[fftsize];
	corr = new float[fftsize];
	decodedbits = new float[size];
	test_buff = 0;
	y2_1 = new float[size];   //
	y2_2 = new float[size];
	position = 0;
	my_index = 0;
	programs = new AEchoHidingProgram[numPrograms];
	programs[0].params[kDelay0] = 128.0f;
	programs[0].params[kDelay1] = 256.0f;
	programs[0].params[kAlfa] = 0.5f;
	programs[0].params[kMode] = 0.0f;
	programs[0].params[kVSTblok] = getBlockSize();
	programs[0].params[kWtmblok] = 512.0f;
	programs[0].params[kWitchBlok] = 0.0f;
	programs[0].params[kWatermark] = 0.0f;
	strcpy (programs[0].name,"Echo Hiding Wtm");
	strcpy (programs[0].Wtm,"WATERMARK!");

	strcpy (Wtm,"WATERMARK!");
	NumberOfBits=Wtm2Bin(Wtm);//Vytvoření bitové posloupnosti (výsledek uložen v proměnné "bits", funkce vrací počet bitů)

	if (programs)
		setProgram (0);

	setNumInputs (2);	// stereo input
	setNumOutputs (2);	// stereo output
	canProcessReplacing ();
	setUniqueID ('AWtm');	// this should be unique, use the Steinberg web page for plugin Id registration

	// create the editor
	editor = new Editor(this,pSize,Wtm);
	resume ();		// flush buffer
}

//------------------------------------------------------------------------
AEcho_Hiding_Wtm::~AEcho_Hiding_Wtm ()
{
	if (bufferL)
		delete[] bufferL;
	if (bufferR)
		delete[] bufferR;
	if (fftbuffer)
		delete[] fftbuffer;
	if (ifftbuffer)
		delete[] ifftbuffer;
	if (decodedbits)
		delete[] decodedbits;
	if (y2_1)
		delete[] y2_1;
	if (y2_2)
		delete[] y2_2;
	if (programs)
		delete[] programs;

	GLeditor->close();
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::setProgram (VstInt32 program)
{
	int i;
	AEchoHidingProgram* prog = &programs[program];
	curProgram = program;
	for(i=0;i<numParams;i++){
		setParameter(i, prog->params[i]);
		if (editor)
			((AEffGUIEditor*)editor)->setParameter (i, prog->params[i]);
	}
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::setWatermark (char wattermark[])
{	
	strcpy(programs[curProgram].Wtm,wattermark);
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::setProgramName (char *name)
{
	strcpy (programs[curProgram].name, name);
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::getProgramName (char *name)
{
	/*if (!strcmp (programs[curProgram].name, "Init"))
	sprintf (name, "%s %d", programs[curProgram].name, curProgram + 1);
	else*/
	strcpy (name, programs[curProgram].name);
}

//-----------------------------------------------------------------------------------------
bool AEcho_Hiding_Wtm::getProgramNameIndexed (VstInt32 category, VstInt32 index, char* text)
{
	if (index < kNumPrograms)
	{
		strcpy (text, programs[index].name);
		return true;
	}
	return false;
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::resume ()
{
	memset (bufferL, 0, size * sizeof (float));
	memset (bufferR, 0, size * sizeof (float));
	memset (fftbuffer, 0, fftsize * sizeof (float));
	memset (ifftbuffer, 0, fftsize * sizeof (float));
	memset (one, 0, 10000 * sizeof (float));
	memset (zer, 0, 10000 * sizeof (float));
	memset (corr, 0, fftsize * sizeof (float));
	memset (decodedbits, 0, size * sizeof (float));
	memset (SumWtm, 0, Wtmlength*8 * sizeof (int));
	memset (y2_1, 0, size * sizeof (float));
	memset (y2_2, 0, size * sizeof (float));
	
	AudioEffectX::resume ();
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::suspend ()
{	
	memset (bufferL, 0, size * sizeof (float));
	memset (bufferR, 0, size * sizeof (float));
	AudioEffectX::suspend ();
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::setParameter (VstInt32 index, float value)
{
	AEchoHidingProgram* prog = &programs[curProgram];
	params[index] = value;
	prog->params[index] = value;
}
//------------------------------------------------------------------------
float AEcho_Hiding_Wtm::getParameter (VstInt32 index)
{	
	return params[index];
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::getParameterName (VstInt32 index, char *label)
{
	switch (index)
	{
	case kDelay0 :		strcpy (label, "Delay0");	break;
	case kDelay1 :		strcpy (label, "Delay1");	break;
	case kAlfa :		strcpy (label, "Alfa");		break;
	case kWatermark :	strcpy (label, "Watermark");break;
	}
}
//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::getParameterDisplay (VstInt32 index, char *text)
{
	/*if (index == kOut)
	{
	dB2string (params[index], text, kVstMaxParamStrLen);
	} else*/ 
	float2string(params[index], text, kVstMaxParamStrLen);
}

//------------------------------------------------------------------------
void AEcho_Hiding_Wtm::getParameterLabel (VstInt32 index, char *label)
{
	switch (index)
	{
	case kDelay0 :		strcpy (label, "Sample");	break;
	case kDelay1 :		strcpy (label, "Sample");	break;
	case kAlfa :		strcpy (label, "Amplitude");break;
	case kWatermark :	strcpy (label, "Bits");		break;

	}
}
//------------------------------------------------------------------------
bool AEcho_Hiding_Wtm::getEffectName (char* name)
{
	strcpy (name, "Echo_Hiding_Wtm");
	return true;
}
//------------------------------------------------------------------------
bool AEcho_Hiding_Wtm::getProductString (char* text)
{
	strcpy (text, "Echo_Hiding_Wtm");
	return true;
}
//------------------------------------------------------------------------
bool AEcho_Hiding_Wtm::getVendorString (char* text)
{
	strcpy (text, "Steinberg Media Technologies");
	return true;
}
//---------------------------------------------------------------------------
void AEcho_Hiding_Wtm::processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames)
{
	float* inL = inputs[0];
	float* inR = inputs[1];
	float* outL = outputs[0];
	float* outR = outputs[1];

	if(params[kWitchBlok]== 0.0f){
		N = (int)sampleFrames;
	}
	else if(params[kWitchBlok]==1.0f){ 
		N = (int)params[kWtmblok];
	}
	//refresh pri stisku tlacitka Inserting,Decoding nebo při změně parametů(velikost bloku,vodoznak)
	if(params[kWatermark]==1.0f){
		setWatermark(Wtm);
		NumberOfBits=Wtm2Bin(Wtm); //Vytvoření bitové posloupnosti vodoznaku a získání počtu bitů
		blockCounter=0;
		BitsCounter=0;
		position =0;
		Energy=0.0f;
		canWatermark=false;
		stepDec=(Wtmlength+1)*8;
		syncposition = 0;
		params[kWatermark]=0.0f;
	}

	//params[kVSTblok]=sampleFrames;
	if (editor){
		((AEffGUIEditor*)editor)->setParameter(kVSTblok,N);

		((AEffGUIEditor*)editor)->setParameter (kWavDispSize, N);
	}
	//Mód Plug-inu (Inserting,Decoding)
	if(params[kMode]==0.0f) //Proces vkládání vodoznaku
		subProcessInserting(inL, inR, outL, outR, sampleFrames);
	else	subProcessDecoding (inL, inR, outL, outR, sampleFrames);

}
//------------------------------------------------------------------------------------------------------------
void AEcho_Hiding_Wtm::subProcessInserting (float *inL,float *inR, float *outL, float *outR, long sampleFrames)
{
	L1=(int)ceil(N/3.0);
	L2=N-L1;
	L3=N;
	delay0=(int)params[kDelay0];
	delay1=(int)params[kDelay1];
	float causalityL,causalityR;
	float Te=0.00001f;

	//for(int i = 0;i < sampleFrames ;i++)	
	while(--sampleFrames >= 0) //0:1:512
	{
		//Naplnění bufferu(slouží k výběru zpožděných vzorků)
		bufferL[position] = *inL;
		bufferR[position] = *inR;
		bufferL[position+N] = *inL;
		bufferR[position+N] = *inR;
		//Výpočet krátkodobé energie(slouží k uřčení charakteru zvuku, pokud je větší než T je možno vložit vodoznak)
		Energy = Energy + (*inL)*(*inL);
		//Výpočet funkce počtu průchodu nulou(slouží k určení char. zvuku[vložení vodoznaku])

		if(blockCounter == 0 || canWatermark == false){
			if(outL)
				*outL++ = (*inL++);
			if(outR)
				*outR++ = (*inR++);
		} else{

		causalityL = 0.0f;
		causalityR = 0.0f;
		//causalityL =params[kAlfa]*bufferL[N-delay0-2+position]*(1.0f-mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3)) + params[kAlfa]*bufferL[N-delay1-2+position]*mixer_signal(1.0f/N,position,bits,BitsCounter,L1,L2,L3);
		//causalityR = params[kAlfa]*bufferL[N-delay0-2+position]*(1.0f-mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3)) + params[kAlfa]*bufferR[N-delay1-2+position]*mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3);
		
		if(outL)
			*outL++ =(*inL++) + params[kAlfa]*bufferL[N-delay0+position]*(1.0f-mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3)) + params[kAlfa]*bufferL[N-delay1+position]*mixer_signal(1.0f/N,position,bits,BitsCounter,L1,L2,L3)+causalityL;
		if(outR)
			*outR++ =(*inR++) + params[kAlfa]*bufferL[N-delay0+position]*(1.0f-mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3)) + params[kAlfa]*bufferR[N-delay1+position]*mixer_signal(1/N,position,bits,BitsCounter,L1,L2,L3)+causalityR;
		}
		if(++position>N){	//pozice 0:blocksize
			if(blockCounter>0 & canWatermark==true)
				BitsCounter++;
			if(BitsCounter>NumberOfBits-1)
				BitsCounter=0;

			if(Energy/N > Te){
				canWatermark=true;
			}

			Energy=0.0f;
			position = 0;
			blockCounter++;
		}
	}

	if((int)params[kVSTblok]==N){
		if(blockCounter>0 & canWatermark==true)
			BitsCounter++;
		if(BitsCounter>NumberOfBits-1)
			BitsCounter=0;

		if(Energy/N > Te){
			canWatermark=true;
		}

		Energy=0.0f;
		position=0; //vždy pro každý blok opakující se index od 0 do blocksize(512)
		blockCounter++;
	}
}
//-----------------------------------------------------------------------------------------------------------
void AEcho_Hiding_Wtm::subProcessDecoding (float *inL,float *inR, float *outL, float *outR, long sampleFrames)
{
	delay0=(int)params[kDelay0];
	delay1=(int)params[kDelay1];
	float Te=0.00001f;
	float x0=0;
	float x1=0;

	while(--sampleFrames >= 0) //0:1:512
	{
		bufferL[position] = (*inL);
		if(outL)
			*outL++ = (*inL++);
		if(outR)
			*outR++ = (*inR++);

		Energy = Energy + (*inL)*(*inL);

		if (editor)
			((AEffGUIEditor*)editor)->setParameter (kWavDisp, corr[position]);

		if(++position>N){

			if(canDetect == true){
				memset (fftbuffer, 0, N * sizeof (float));
				memset (ifftbuffer, 0, N * sizeof (float));
				memset (corr, 0, N * sizeof (float));
				fft(bufferL,fftbuffer,9,0);
				for(int k=0;k<N;k++)
				{
					fftbuffer[k]=log(sqrt(bufferL[k]*bufferL[k]*bufferL[k]*bufferL[k] 
					+ fftbuffer[k]*fftbuffer[k]*fftbuffer[k]*fftbuffer[k]));
				}
				fft(fftbuffer,ifftbuffer,9,1);
				for(int m=0;m<N;m++){
					for(int n=0;n<N-m;n++){

						corr[m]=fftbuffer[n]*fftbuffer[n+m];

					}
					corr[m]= abs(corr[m]);
				}
				x1=corr[delay1-1]+corr[delay1];
				x0=corr[delay0-1]+corr[delay0];
				if(x1 > x0){

					one[BitsCounter]=x1;
					DecBits[BitsCounter]=1;
				}
				else{ 
					zer[BitsCounter]=x0;
					DecBits[BitsCounter]=0;
				}

				//Dekódování jednotlivých bitů a převod na znaky
				if(BitsCounter>=stepDec){ //>=99
					Decode(DecBits,BitsCounter);
					stepDec+=(Wtmlength+1)*8;
				}


				if(++BitsCounter>=20000){
					BitsCounter=0;
					syncposition=0;
				}

			}
			if(Energy/N > Te)
				canDetect = true;

			position = 0;
			blockCounter++;
			Energy=0.0f;
		}
	}//konec cyklu while 
}
//---------------------------------------------------------------------------------------------------
float AEcho_Hiding_Wtm::mixer_signal(float f,int position,char* bits,int counter,int L1,int L2,int L3)
{
	float y;
	int counter1=counter-1;
	int counter2=counter;
	int counter3=counter+1;

	if (counter == 0){        
		counter1 = (int)strlen(bits)-1;
		counter3 = counter+1;
	}
	if (counter == ((int)strlen(bits))-1){ 
		counter1 = ((int)strlen(bits))-2;
		counter3 = 0;
	}

	if(((int)bits[counter1]==(int)'1') & ((int)bits[counter2]==(int)'1') & ((int)bits[counter3]==(int)'1')) 
		if (position <= L3)
			return y = 1.0f;


	if(((int)bits[counter1]==(int)'1') & ((int)bits[counter2]==(int)'1') & ((int)bits[counter3]==(int)'0')){ 
		if (position <= L2)
			return y = 1.0f;

		if ((position > L2) & (position <= L3))
			return y = (1.0f/L1)*(L3-position);
	}  

	if(((int)bits[counter1]==(int)'1') & ((int)bits[counter2]==(int)'0') & ((int)bits[counter3]==(int)'1')){ 
		if (position <= L2)
			return y = 0.0f;

		if ((position > L2) & (position <= L3))
			return y = (1.0f/L1)*(position-L2);
	} 

	if(((int)bits[counter1]==(int)'1') & ((int)bits[counter2]==(int)'0') & ((int)bits[counter3]==(int)'0')){ 
		if (position <= L3)
			return y = 0.0f;
	} 

	if(((int)bits[counter1]==(int)'0') & ((int)bits[counter2]==(int)'1') & ((int)bits[counter3]==(int)'0')){ 
		if(position <= L2)
			return y = 1.0f;
		if((position > L2) & (position <= L3))
			return y = (1.0f/L1)*(L3-position);
	}

	if(((int)bits[counter1]==(int)'0') & ((int)bits[counter2]==(int)'1') & ((int)bits[counter3]==(int)'1')){ 
		if (position <= L3)
			return y = 1.0f;
	}

	if(((int)bits[counter1]==(int)'0') & ((int)bits[counter2]==(int)'0') & ((int)bits[counter3]==(int)'1')){ 
		if (position <= L2)
			return y = 0.0f;
		if ((position > L2) & (position <= L3))
			return y = (1.0f/L1)*(position-L2);
	} 

	if(((int)bits[counter1]==(int)'0') & ((int)bits[counter2]==(int)'0') & ((int)bits[counter3]==(int)'0')){ 
		if (position <= L3)
			return y = 0.0f;
	}
	return y=0.0f;
}
//--------------------------------------
int AEcho_Hiding_Wtm::Wtm2Bin (char* Wtm)
{
	int znaku=((int)strlen(Wtm));
	int bitu=Wtmlength*8;
	strcpy(bits,"11111111");
	for(int i=1;i<Wtmlength+1;i++){
		int num = (int)Wtm[i-1];

		for(int j=0;j<8;j++){
			if(num%2==0)
				bits[j+i*8]='0';
			else bits[j+i*8]='1';
			num = num >> 1;
		}

	}
	bits[(znaku+1)*8]='\0';

	return bitu+8;
}
//-------------------------------------------------
int AEcho_Hiding_Wtm::Bin2Wtm (int DecBits[],int i)
{

	int num = 0;
	int nas = 1;
	int bit = 0;

	for(int n=0;n<8;n++){

		if(DecBits[n+i] == 1)
			bit=1;
		else bit = 0;
		num = num + bit*nas;
		nas = nas*2;

	}
	if(num == 0)
		return 48;

	return num;
}
//--------------------------------------------------------------
void AEcho_Hiding_Wtm::fft(float *x,float *y,int order,int param)
{
	unsigned int n,l,e,f,i,j,o,o1,j1,i1,k;
	double u,v,z,c,s,p,q,r,t,w,a;
	int FFT=0;

	n=1u<<order;

	for(l=1;l<=order;l++)
	{
		u=1.0;
		v=0.0;
		e=1u<<(order-l+1);
		f=e/2;

		z=M_PI/f;

		c=cos(z);
		s=sin(z);

		if(param==FFT) s=-s;
		//if(param==IFFT) s=s;	

		for(j=1;j<=f;j++)
		{
			for(i=j;i<=n;i+=e)
			{
				o=i+f-1;
				o1=i-1;
				p=x[o1]+x[o];
				r=x[o1]-x[o];
				q=y[o1]+y[o];
				t=y[o1]-y[o];
				x[o]=r*u-t*v;
				y[o]=t*u+r*v;
				x[o1]=p;
				y[o1]=q;
			}
			w=u*c-v*s;
			v=v*c+u*s;
			u=w;
		}
	}
	j=1;
	for(i=1;i<n;i++)
	{
		if(i<j)
		{
			j1=j-1;
			i1=i-1;
			p=x[j1];
			q=y[j1];
			x[j1]=x[i1];
			y[j1]=y[i1];
			x[i1]=p;
			y[i1]=q;
		}

		k=n/2;

		while(k<j)
		{
			j=j-k;
			k=k/2;
		}

		j+=k;
	}

	if(param==FFT) return;

	a=1.0/n;
	for(k=0;k<n;k++)
	{
		x[k]*=a;
		y[k]*=a;
	}
	return;
}
//----------------------------------------------------------
void AEcho_Hiding_Wtm::Decode(int DecBits[],int BitsCounter)
{
	int znak=0;
	StartDec = 0;
	StopDec = 0;
	//nalezení synchronizační sekvence
	for(int i = 0+syncposition;i<BitsCounter;i++){

		znak = Bin2Wtm(DecBits,i);
		if(znak == 255){ //sync
			sync = true;
			syncposition=i;
			StartDec=syncposition+8;
			StopDec = StartDec+Wtmlength*8;

			//if (editor)
			//((AEffGUIEditor*)editor)->setParameter (kCharCounter,(float)znak);
			break;
		}

	}
	if(sync){
		for(int n = StartDec;n<StopDec;n=n+8){

			znak = Bin2Wtm(DecBits,n);
			if(znak == 255){
				syncposition = n;
				break;	
			}
			if (editor)
				((AEffGUIEditor*)editor)->setParameter (kCharCounter,(float)znak);
			syncposition = n+8;
		}
	}
	sync = false;
	return;
}
//-------------------------------------------------------
float AEcho_Hiding_Wtm::triangle (float f, float position)
{
	float maxpos =  f/2;
	float triang = 0.0f;

	if(position <= (maxpos))
		triang = (1/maxpos)*position;

	if(position > (maxpos) && position <= 2*(maxpos))
		triang = (1/maxpos)*(2*maxpos - position);

	/*if(position > 2*(maxpos) && position <= (3)*(maxpos))
	triang = (1/maxpos)*position;
	//triang = -(1/maxpos)*(position - 2*maxpos);

	if(position > (3)*(maxpos) && position <= 4*(maxpos))
	triang = (1/maxpos)*(2*maxpos - position);
	//triang = -(1/maxpos)*(4 * maxpos - position);

	*/
	return triang;
}