/*
 * SPDX-License-Identifier: GPL-3.0+
 * License-Filename: ....
 * 
 * Copyright (C) 2008 to 2011 Masanori Morise
 * Copyright (C) 2008 to 2011（ZteerのUTAU関連物置き場）
 * Copyright (C) 2017 Tobias "Tomoko" Platen
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see: <http://www.gnu.org/licenses/>.
 */
 // Lyric Hyphenator : TODO
 
#include "config.h"

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tn_fnds.h"
#include <espeak-ng/speak_lib.h>

#define PI 3.1415926535897932384
#define MAX_FFT_LENGTH 2048
#define FLOOR_F0 90.0
#define DEFAULT_F0 500.0

//#define DEBUG_PULSE_LOCATIONS

double* tn_fnds_input;
extern double midi_pitch;
int tn_fnds_input_index;
extern int samplerate;
int total_length_should_be=0;

static int tn_fnds_preutter=0;
static int tn_fnds_vconsonant=0;
static float tn_fnds_length=0;
static int tn_fnds_left=0;
static int tn_fnds_right=0;
static int tn_fnds_last_preutter=0;

void tn_fnds_param(int preutter,int vconsonant,float length, int left,int right)
{
    tn_fnds_preutter = preutter;
    tn_fnds_vconsonant = vconsonant;
    tn_fnds_length = length;
    tn_fnds_left = left;
    tn_fnds_right = right;
    //fprintf(stderr,"length %f preuttr %i\n",length,preutter);
}

void window(double *wav, int length)
{
    int i;
    for(i = 0;i < length; i++)
    {
			wav[i] = wav[i] * (0.5 - 0.5*cos(2.0*PI*(double)(i+1)/((double)(length+1))));
    }
}
void agc(double *wav, int length)
{
    float maxValue = 0;
    int i;
    for(i = 0;i < length; i++)
    {
        if(fabs(wav[i])>maxValue) maxValue=fabs(wav[i]); 
    }
    if(maxValue>0)
    {
        for(i = 0;i < length; i++)
        {
            wav[i]/=maxValue;
            wav[i]*=0.5; 
        }
    }
}

void printmax(double *wav, int length,char* label) //for debugging
{
    float maxValue = 0;
    int i;
    for(i = 0;i < length; i++)
    {
        if(fabs(wav[i])>maxValue) maxValue=fabs(wav[i]); 
    }
    fprintf(stderr,"max %s = %f\n",label,maxValue);
   
}

float interp_linear(float* x,float* y,int nx,float ref)
{
    int i;
    for(i=0; i<nx-1; i++)
    {
        if(ref>=x[i] && ref <= x[i+1])
        {
            float x1=x[i];
            float x2=x[i+1];
            float tmp = (ref-x1)/(x2-x1);
            return y[i]*(1-tmp)+y[i+1]*tmp;
        }
    }
    fprintf(stderr,"INTERP_LINEAR: out of range\n");
    return NAN;
}

float copyIn(double* buffera,double* bufferb,int buffer_length,int* pulseLocations,int pCount,float pos)
{
    
    int i,j;
    int found = -1;
    int ref = pos*samplerate;
    int pulse0 = 0;
    for(i=0;i<pCount-1;i++)
    {
        if(pulseLocations[i-1]<=ref && pulseLocations[i] > ref) {found = i; break;}
    }
    if(ref<pulseLocations[0])
    {
		found = 0;
		pulse0 = 1;
	}
    if(found>=0)
    {
        //fprintf(stderr,"copyIn %i %i %i %i\n",pulseLocations[found-1],pulseLocations[found],ref,tn_fnds_input_index);
        for(i=0;i<buffer_length;i++)
        {
            j = i - buffer_length/2 + pulseLocations[found-1];
            if(j>0 && j<tn_fnds_input_index) buffera[i] = tn_fnds_input[j];
            else buffera[i] = 0;
        }
        for(i=0;i<buffer_length;i++)
        {
            j = i - buffer_length/2 + pulseLocations[found];
            if(j>0 && j<tn_fnds_input_index) bufferb[i] = tn_fnds_input[j];
            else bufferb[i] = 0;
        }
        if(pulse0) return 0.5;
        float zz = pulseLocations[found-1]-pulseLocations[found];
        float nn = pulseLocations[found-1]-ref;
        return (nn/zz);
        
    }
    fprintf(stderr,"copy in failed\n");
    return 2.0;
}

void ola_agc(double* output_buffer,int output_buffer_length,double* buffer,int buffer_length,int pos)
{
    int i,j;
    int overflow_counter = 0;
    float max_pos=0;
    float max_neg=0;
    
    for(int i=0;i<buffer_length;i++)
    {
         j = i - buffer_length/2 + pos;
         if(j>0 && j<output_buffer_length) { 
			 output_buffer[j] += buffer[i];
			 if(fabs(output_buffer[j])>0.5) {;
				 if(output_buffer[j] > 0 &&  output_buffer[j]>max_pos) max_pos =  output_buffer[j];
				 if(output_buffer[j] < 0 && -output_buffer[j]>max_neg) max_neg = -output_buffer[j];
				 overflow_counter++;
			 }
		 }
    }
    if(overflow_counter==0) return;
    for(int i=0;i<buffer_length;i++)
    {
         j = i - buffer_length/2 + pos;
         if(j>0 && j<output_buffer_length) {
			 if(output_buffer[j]>0 && max_pos>0) {output_buffer[j]/=max_pos; output_buffer[j]*=0.50; } 
			 if(output_buffer[j]<0 && max_neg>0) {output_buffer[j]/=max_neg; output_buffer[j]*=0.50; }
			 
		 }
	}
    
}

static void outp(double* output,int length)
{
    extern t_espeak_callback *synth_callback;
    if(synth_callback)
    {
        short* output2 = malloc(sizeof(short)*length);
        for(int i=0;i< length;i++)
        {
            if(output) output2[i] = output[i]*32767.0;
            else output2[i] = 0;
        }
        espeak_EVENT event;
        event.type = espeakEVENT_LIST_TERMINATED; // indicates end of event list
        event.unique_identifier = 0;
        event.user_data = 0;
        synth_callback(output2,  length, &event);
        free(output2);
    }
}

int findWedge(int start,int T0,float factor)
{
    int wedge;
    double peak=0;
    int i;
    for(i = start;i < T0*factor+1+start;i++)
    {
        if(i>tn_fnds_input_index-1) break;
        if(tn_fnds_input[i] > peak)
        {
            peak = tn_fnds_input[i];
            wedge = i;
        }
    }
    return wedge;
}

int getPulseLocations(int T0,int* pulseLocations)
{
    int pCount=0;
    int wedge = findWedge(0,T0,2);
    if(pulseLocations) pulseLocations[pCount] = wedge;
    pCount++;
    int i=wedge;
    while(i<tn_fnds_input_index-T0*2){
        wedge = findWedge(i+T0*0.25,T0,1);
        if(pulseLocations) pulseLocations[pCount] = wedge;
        i = wedge;
        pCount++;
    }
    return pCount;
}

extern int sinsy_wavegen_samples;
void tn_fnds()
{
    
    
    int T0 = (int)((samplerate / midi_pitch)+0.5);
    int i;
    
   
    
    i=0;
    
    
    
    int pCount=getPulseLocations(T0,0);
    int* pulseLocations = malloc(sizeof(int)*pCount);
    getPulseLocations(T0,pulseLocations);
    
    int cutoff = pulseLocations[pCount-10]+T0;//skip the last 10 pulses
    if(cutoff>=tn_fnds_input_index) cutoff=tn_fnds_input_index;
   
    
    #ifdef DEBUG_PULSE_LOCATIONS
    for(i=0;i<tn_fnds_input_index;i++)
    {
        tn_fnds_input[i]*=0.5;
    }
    
    for(i=0;i<pCount;i++)
    {
       tn_fnds_input[pulseLocations[i]] = 1;
    }
    
    outp(tn_fnds_input,tn_fnds_input_index);
    return;
    #endif
    
    //done analysis
    
    #define N 3
    // units: seconds
    float p1 = 25.0/1000;
    float p2 = 25.0/1000;
    
    float x_end = tn_fnds_length-tn_fnds_preutter*1.0/samplerate;
    
    //fprintf(stderr,"l/r %f %f\n",tn_fnds_left*1.0/samplerate,tn_fnds_right*1.0/samplerate);
    x_end -= tn_fnds_left*1.0/samplerate;
    x_end -= tn_fnds_right*1.0/samplerate;
    
    x_end += tn_fnds_last_preutter*1.0/samplerate;
    
    //fprintf(stderr,"x_end: %f\n",x_end);
    float y_end = cutoff*1.0/samplerate;
    float x[N] = {0.0,p1,x_end};
    float y[N] = {0.0,p1,y_end};
    
    int output_length = x_end*samplerate;
    double* output = malloc(sizeof(double)*output_length);
    
    //fprintf(stderr,"total_length_should_be: %i\n",output_length+tn_fnds_left+tn_fnds_right);
    total_length_should_be = output_length+tn_fnds_left+tn_fnds_right;
   
    memset(output,0,sizeof(float)*output_length);
    
    double currentTime = 0;
    double currentIndex = 0;
    double* buffer = malloc(sizeof(double)*T0*2);
    double* buffera = malloc(sizeof(double)*T0*2);
    double* bufferb = malloc(sizeof(double)*T0*2);
    while(currentTime<x_end)
    {
        currentTime += 1.0/midi_pitch;
        currentIndex = currentTime*samplerate;
        
        if(currentTime>=x_end) break;
        
        float pos = interp_linear(x,y,4,currentTime);
        
        int t = (int)currentIndex;
        memset(bufferb,0,sizeof(double)*T0*2);
        memset(buffera,0,sizeof(double)*T0*2);
        float interp = copyIn(buffera,bufferb,T0*2,pulseLocations,pCount,pos);
        for(i=0;i<T0*2;i++)
        {
            buffer[i]=buffera[i]*(1-interp)+bufferb[i]*interp;
        }
        window(buffer,T0*2);
        agc(buffer,T0*2);
        //printmax(buffer,T0*2,"buffer");
        //printmax(buffera,T0*2,"buffera");
        //printmax(bufferb,T0*2,"bufferb");
        //fprintf(stderr,"-------------------------\n");
        
        
        
        //outp(buffer,T0*2); only if debug
        ola_agc(output,output_length,buffer,T0*2,t);
    }
    #undef N
    //done synthesis, copy out
    outp(output,output_length);
    sinsy_wavegen_samples += output_length;
}


void tn_fnds_next()
{
     //fprintf(stderr,"total_length_is: %i\n",sinsy_wavegen_samples);
     if(sinsy_wavegen_samples!=total_length_should_be){
          fprintf(stderr,"FATAL ERROR: total_length does not match (%i!=%i)\n",sinsy_wavegen_samples,total_length_should_be);
          exit(1);
     }
     sinsy_wavegen_samples = 0;
     
     tn_fnds_last_preutter = tn_fnds_preutter;
}

void tn_fnds_silence(int preutter,float length)
{
    float silence = length*samplerate-preutter;
    tn_fnds_last_preutter = preutter;
    outp(0,silence);
}
