import os
import subprocess
import re
from random import randint

import logzero
from logzero import logger
from logzero import setup_logger

CONFIDENCE_THRESH = 0.02

class VaporSong:

	# Slows down Track
	
	def slow_down(src, rate, dest):
		cmd = "sox -G -D " + src + " " + dest + " speed " + str(rate)
		os.system(cmd)
		return dest

	# Adds Reverb
	
	def reverbize(src, dest):
		cmd = "sox -G -D " + src + " " + dest + " reverb 100 fade 5 -0 7" # idk what this does tbh, https://stackoverflow.com/a/57767238/8386344
		os.system(cmd)
		return dest

	
	# Crops "src" from "start" plus "start + dur" and return it in "dest"
	def crop(src,dest,start,dur):
		cmd = "sox " + src + " " + dest + " trim " + " " + str(start) + " " + str(dur)
		os.system(cmd)

	
	# Randomly crops a part of the song of at most max_sec_len.
	def random_crop(src, max_sec_len, dest):
		out = subprocess.check_output(["soxi","-D",src]).rstrip()
		f_len = int(float(out))
		if (f_len <= max_sec_len):
			os.system("cp " + src + " " + dest)
			return
		else:
			start_region = f_len - max_sec_len
			start = randint(0,start_region)
			VaporSong.crop(src,dest,start,max_sec_len)

	
	# Given a file, returns a list of [beats, confidence], executable based on audibo's test-beattracking.c 
	# TODO: Move away from executable and use aubio's Python module
	def fetchbeats(src):
		beat_matrix = []
		if os.name == 'posix':
			beats = subprocess.check_output(["noah", "get-beats",src]).rstrip()
		else:
			beats = subprocess.check_output(["get-beats",src]).rstrip()
		beats_ary = beats.splitlines()
		for i in beats_ary:
			record = i.split()
			record[0] = float(record[0])/1000.0
			record[1] = float(record[1])
			beat_matrix.append(record)
		return beat_matrix

	# Splits an audio file into beats according to beat_matrix list
	
	def split_beat(src,beat_matrix):
		split_files = []
		for i in range(0,len(beat_matrix)-1):

			if(beat_matrix[i][1] > CONFIDENCE_THRESH):
				dur = (beat_matrix[i+1][0] - beat_matrix[i][0])
				out = src.split(".")[0]+str(i)+".wav"
				VaporSong.crop(src,out,beat_matrix[i][0],dur)
				split_files.append(out)
		return split_files

	# Combines a list of sections
	
	def combine(sections,dest):
		tocomb = []
		tocomb.append("sox")
		tocomb.append("-G")
		for section in sections:
			for sample in section:
				tocomb.append(sample)
		tocomb.append(dest)
		tmpFileLimit = len(tocomb) + 256 # in case the program messes up, it does not actually frick up your system
		n = str(tmpFileLimit)
		#logger.info("Setting file limit to ", n)
		os.system("ulimit -n " + n)
		subprocess.check_output(tocomb)
		return dest

	# Arbitrarily groups beats into lists of 4, 6, 8, or 9, perfect for looping.
	
	def generate_sections(ary):
		sections = []
		beats = [4,6,8,9]
		index = 0
		while(index != len(ary)):
			current_beat = beats[randint(0,len(beats)-1)]
			new_section = []
			while((current_beat != 0) and (index != len(ary))):
				new_section.append(ary[index])
				current_beat -= 1
				index += 1
			sections.append(new_section)
		return sections

	
	# given a list of sections, selects some of them and duplicates them, perfect for that vaporwave looping effect
	def dup_sections(sections):
		new_section = []
		for section in sections:
			new_section.append(section)
			if(randint(0,1) == 0):
				new_section.append(section)
		return new_section

	# a passage is a list of sections. This takes some sections and groups them into passages.
	
	def make_passages(sections):
		passages = []
		index = 0
		while(index != len(sections)):
			passage_len = randint(1,4)
			passage = []
			while(index != len(sections) and passage_len > 0):
				passage.append(sections[index])
				index += 1
				passage_len -= 1
			passages.append(passage)
		return passages

	# Given all of our passages, picks some of them and inserts them into a list some number of times.
	
	def reorder_passages(passages):
		new_passages = []
		passage_count = randint(5,12)
		while(passage_count != 0):
			passage = passages[randint(0,len(passages)-1)]
			passage_count -= 1
			dup = randint(1,4)
			while(dup != 0):
				dup -= 1
				new_passages.append(passage)
		return new_passages

	# converts a list of passages to a list of sections.
	
	def flatten(passages):
		sections = []
		for passage in passages:
			for section in passage:
				sections.append(section)
		return sections

	# It's all coming together
	
	def vaporize_song(fname, title):
		logger.info("Slowing down the music")
		VaporSong.slow_down(fname, 0.7, "beats/out.wav")
		#logger.info("Cropping")
		#VaporSong.random_crop("beats/out.wav",150,"beats/outcrop.wav")
		logger.info("Doing Beat Analysis")
		bm = VaporSong.fetchbeats("beats/out.wav")
		logger.info("Split into beats")
		splitd = VaporSong.split_beat("beats/out.wav",bm)
		#group beats to sections
		logger.info("Divide into sections")
		sections = VaporSong.generate_sections(splitd)
		logger.info("Duping Sections")
		sdup = VaporSong.dup_sections(sections)
		# group sections into passages
		paslist = VaporSong.make_passages(sdup)
		# reorder packages
		pasloop = VaporSong.reorder_passages(paslist)
		sectionflat = VaporSong.flatten(pasloop)
		logger.info("Mastering & Reverbing")
		VaporSong.combine(sectionflat,"beats/out_norev.wav")
		VaporSong.reverbize("beats/out_norev.wav","./" + (re.sub(r"\W+|_", " ", title)).replace(" ","_") + ".wav")
		logger.info("Generated V A P O R W A V E")