#!/usr/bin/env python
"""Render Ambient-Mix XML to MP3"""

import untangle, pydub, argparse, math, os
from typing import List

__author__ = "Kevin Whitaker"
__license__ = "GPL"
__version__ = "1.0"

human_unit_to_sec = {
        "1m": 60,
        "10m": 600,
        "1h": 3600
}

def convert_volume_to_decibel(volume: int):
    return -(math.log10(1+volume/100))

def convert_balance_to_pan(balance: int):
    return (balance*2)/100

def load_sample_to_segment(audio_file):
    if not os.path.exists(audio_file):
        print("Needed audio file "+audio_file+" does not exist.")
        exit(1)
    return pydub.AudioSegment.from_mp3(audio_file)

class Channel():
    base_segment: pydub.AudioSegment
    length: int = 3600
    local_url: str = ""
    name: str = ""
    id: int = -1
    url: str = ""
    muted: bool = False
    volume: int = 100 #between 0 and 100
    balance: int = 0 #between -50 and +50
    random: bool = False
    random_counter: int = 1
    random_unit: int = human_unit_to_sec["1h"]
    crossfade: bool = False
    
    def __init__(self, length: int, source_dir: str, id: int):
        self.length = length
        self.id = id
        self.local_url = source_dir+"/"+str(id)+".mp3"
        self.base_segment = pydub.AudioSegment.silent(duration= length*1000)
    
    def render_channel(self):
        if not self.muted:
            inital_audio = load_sample_to_segment(self.local_url)
            gained_audio = inital_audio.apply_gain(convert_volume_to_decibel(self.volume))
            panned_audio = gained_audio.pan(convert_balance_to_pan(self.balance))
            if self.random:
                trigger_every = int(self.random_unit/self.random_counter)
                canvas: pydub.AudioSegment = self.base_segment
                for sec in range(trigger_every,self.length,trigger_every):
                    canvas = canvas.overlay(panned_audio,position=sec*1000)
                return canvas
            elif self.crossfade:
                fadein_audio = panned_audio.fade_in(duration=1000)
                crossfaded_audio = fadein_audio.fade_out(duration=1000)
                return self.base_segment.overlay(crossfaded_audio, loop=True)
            else:
                return self.base_segment.overlay(panned_audio, loop=True)
        else:
            return self.base_segment

class Mix():
    channels: List[Channel] = []
    length: int = 3600
    final_segment: pydub.AudioSegment
    
    def __init__(self, xml_file: str, length: int, source_dir: str):
        self.length = length
        self.final_segment = pydub.AudioSegment.silent(duration=length*1000)
        xml = untangle.parse(xml_file)
        for channel_num in range(1,9):
            channel_xml = getattr(xml.audio_template, "channel"+str(channel_num))
            channel = Channel(length, source_dir, channel_xml.id_audio.cdata)
            channel.name = channel_xml.name_audio.cdata
            channel.url = channel_xml.url_audio.cdata
            channel.muted = channel_xml.mute.cdata == "true"
            channel.volume = int(channel_xml.volume.cdata)
            channel.balance = int(channel_xml.balance.cdata)
            channel.random = channel_xml.random.cdata == "true"
            channel.random_counter = int(channel_xml.random_counter.cdata)
            channel.random_unit = human_unit_to_sec[channel_xml.random_unit.cdata]
            channel.crossfade = channel_xml.crossfade.cdata == "true"
            self.channels.append(channel)
            
    def render_channels(self):
        for channel in self.channels:
            self.final_segment = self.final_segment.overlay(channel.render_channel())
    
    def export_final(self, outfile: str):
        self.final_segment.export(outfile, format="mp3")


parser = argparse.ArgumentParser(description="Render Ambient-Mix XML to MP3")
parser.add_argument("-l", help="length of final render in seconds", default=3600, dest="length")
parser.add_argument("-s", help="directory containing audio samples", default="sounds", dest="samples")
parser.add_argument("infile", help="Input XML file defining mix.")
parser.add_argument("outfile", help="Output MP3 file of rendered mix.")
args = parser.parse_args()

if not os.path.exists(args.samples):
    print("Samples directory does not exist.")
    exit(1)

mixer = Mix(args.infile, int(args.length), args.samples)
mixer.render_channels()
mixer.export_final(args.outfile)