import os
from scipy.io import wavfile as wav
from math import ceil
import numpy as np
import difflib
import random
import soundfile as sf
import traceback
from moviepy.editor import VideoFileClip, AudioFileClip

    
def SetAmbientSound(scene_type, library, data):
    samples = library.samples["ambience"]
    sP, fs = sf.read(f"{library.path}/{samples[0]}", dtype='int16')
    sC, fs = sf.read(f"{library.path}/{samples[1]}", dtype='int16')
    sN, fs = sf.read(f"{library.path}/{samples[2]}", dtype='int16')
    sP = np.tile(sP, ceil(len(data)/len(sP)))
    sC = np.tile(sC, ceil(len(data)/len(sC)))
    sN = np.tile(sN, ceil(len(data)/len(sN)))
    
    sP = np.array(sP[:len(data)])
    sP = sP / max(abs(sP))
    sC = np.array(sC[:len(data)])
    sC = sC / max(abs(sC))
    sN = np.array(sN[:len(data)])
    sN = sN / max(abs(sN))

    P, C, N = scene_type
    P = (sP) * P
    C = (sC) * C
    N = (sN) * N
    y = (P + C + N) / 3 
    y = y / max(abs(y))

    sf.write("ambience.wav", y, 44100)
    return y

def replace_audio(video_file: str, audio_file, output_file: str):
    video_clip = VideoFileClip(video_file)
    audio_clip = AudioFileClip(audio_file)
    new_video_clip = video_clip.set_audio(audio_clip)
    new_video_clip.write_videofile(output_file, audio_codec='aac', temp_audiofile='temp-audio.m4a', remove_temp=True)

def getHRTFdata(xy: tuple, dimensions: tuple):

    path = "./HRTF/data/"
    list = [f for f in os.listdir(path)]
    ear = ["left","right"]
    ir = []

    x = (xy[0] - dimensions[0]/2)/dimensions[0]/2 
    y = (xy[1]/dimensions[1]) 
    if x>=0: 
        azimuth = int(x*90)
        xx = ""
    else: 
        azimuth = 360 - int(x*90 + 360)
        xx ="neg"
    elevation = int(y*45)

    for i in range(2):
        filename = f"{xx}{azimuth}az{ear[i]}.wav"
        match = difflib.get_close_matches(filename, list)
        fs, samples = wav.read(path+match[2])
        response=[]
        response = np.array(samples[elevation])

        ir.append(response)
    return ir


def create_empty_audio_samples(info: dict):
    count = (info["total_frames"]*ceil(info["samplerate"]/info["framerate"]))
    samples = [[0,0] for i in range(count)]
    return samples

def read_audio_samples(filepath: str):
    try:
        fs, samples = sf.read(filepath, dtype='int16')
        return fs, samples
    except:
        print("Error while reading .WAV file at: "+str(filepath))

def path_interpolation(object, info: dict):

    interpolated_path = []
    for i in range(len(object.path)):
        if object.path[i][1] != (0,0):
            interpolated_path.append(object.path[i])

    result = []
    for i in range(len(interpolated_path)):
        result.append(interpolated_path[i])
        if i < len(interpolated_path) - 1:
            diff = interpolated_path[i+1][0] - interpolated_path[i][0]
            if diff > 1:
                for j in range(1, diff):
                    result.append((interpolated_path[i][0]+j, interpolated_path[i][1]))
    
    object.path = result

def size_interpolation(object, info: dict):

    interpolated_size = []
    for i in range(len(object.size)):
        if object.size[i][1] != 0:
            interpolated_size.append(object.size[i])
    result = []
    for i in range(len(interpolated_size)):
        result.append(interpolated_size[i])
        if i < len(interpolated_size) - 1:
            diff = interpolated_size[i+1][0] - interpolated_size[i][0]
            if diff > 1:
                for j in range(1, diff):
                    result.append((interpolated_size[i][0]+j, interpolated_size[i][1]))
                    if len(interpolated_size[i]) > 2:
                        result[-1] += interpolated_size[i][2:]

    object.size =  result
def reshape_samples(samples,type: bool):
    if type == True:
        o=[]
        for i in range(len(samples)):
            o.append(samples[i][0])
        return o

    if type == False:
        oL=[]
        oR = []
        for i in range(len(samples)):
            oL.append(samples[i][0])
            oR.append(samples[i][1])
        return oL, oR

def place_audio(object, empty_samples: list, library, output_info, stype):
    folder_path = stype
    output_folder = "audiooutput/"

    try:
        path_interpolation(object, output_info)
        size_interpolation(object, output_info)
    except:
        print("Interpolation failed")

    placed_audio=np.array(reshape_samples(empty_samples,True), dtype="int16")
    aviable_wav = library.samples[object.name]
    if aviable_wav == []:
        print(f"No aviable .wav found for object: {object.name}")
        samples = np.array([0, 0], dtype="int16")   
    else:
        wav_ = aviable_wav[random.randint(0,(len(aviable_wav)-1))]
        try:
            samples_, fs = read_audio_samples(folder_path+"/"+wav_)
        except:
            print("No .wav file found")
            return

    try:
        start_frame = object.path[0][0]
        samples_per_frame = ceil(output_info["samplerate"] / output_info["framerate"])
    except:
        print("startf and spf problem")
    try:
        
        N = int(start_frame*samples_per_frame)
        M = int(output_info["total_frames"] * samples_per_frame) - len(samples_) - N
        nM = ceil(M / len(samples_))
        l = int(output_info["total_frames"] * samples_per_frame) 
        if M < 0:
            placed_audio = np.concatenate((np.zeros(N+random.randint(0,200)), samples_[:M]))
            placed_audio = placed_audio[:l]
        else:
            placed_audio = np.concatenate((np.zeros(N+random.randint(0,200)), np.tile(samples_, nM+1)))
            placed_audio = placed_audio[:l]
    except Exception as e:
        traceback.print_exc()
        print("Problem with placing")
    
    gl = []
    gr = []
    gL = []
    gR = []
    outputLR = []    
    splitted = np.array(np.array_split(placed_audio, int(output_info["total_frames"]))).astype("float32")
    N = object.path[0][0]
    M = object.path[-1][0] + 1
    gl_fade = []
    gr_fade = []
    fadetime = 800
    for i in range(len(splitted)):
        if i >= N and i < M:
            xy = object.path[i-N][1]
            maxsize = output_info["dimensions"][0]*output_info["dimensions"][1]
            vol = float(object.size[i-N][1]/maxsize)

            ir = getHRTFdata(xy, output_info["dimensions"])
            ir = np.array(ir, dtype="int16")
            gl = (np.convolve((splitted[i]*vol), (ir[0]), mode="same"))
            if len(gl_fade) != 0:
                try:
                    gl = np.array([(gl[:400] + gl_fade[:fadetime]) / 2, gl[fadetime:]])
                    gl = np.concatenate(gl)
                except:
                    pass
            try:
                gl_fade = (np.convolve((splitted[i+1]*vol), (ir[0]), mode="same"))
            except:
                pass
            gr = (np.convolve(((splitted[i]*vol)), ((ir[1])), mode="same"))
            if len(gr_fade) != 0:
                try:
                    gr = np.array([(gr[:400] + gr_fade[:fadetime]) / 2, gr[fadetime:]])
                    gr = np.concatenate(gr)
                except:
                    pass
            try:
                gr_fade = (np.convolve((splitted[i+1]*vol), (ir[1]), mode="same"))
            except:
                pass
            gL.extend(gl)
            gR.extend(gr)
        else:
            gL.extend(splitted[i])
            gR.extend(splitted[i])

    gL = np.array(gL)
    gR = np.array(gR)
    outputLR = [gL, gR]

    return outputLR

def sum_audio(data, input_file, scene_type, library):
    np.save("detected.npy", data)
    outputL = np.zeros(len(data[0][0]))
    outputR = np.zeros(len(data[0][0]))

    ambience = SetAmbientSound(scene_type, library, outputL)

    for i in range(len(data)):
        outputL += data[i][0]
        outputR += data[i][1]

    outputL += ambience
    outputR += ambience


    mL = float(max(abs(outputL)))
    mR = float(max(abs(outputR)))
    max_value = abs(max([mL,mR]))

    normalized_outputL = (outputL/max_value)
    normalized_outputR = (outputR/max_value)

    out = np.column_stack((normalized_outputL, normalized_outputR))
    sf.write("output.wav",out, 44100)
    replace_audio(input_file, "output.wav", f"{os.getcwd()}/synthesized.mp4")

    return out

