import threading
import logging
import time
import numpy as np
import librosa
import bisect
import math
from .base_analyzer import BaseAnalyzer

class BeatAnalyzer(BaseAnalyzer):
    def __init__(self,
            rec_time = 5,
            initial_bpm = 120,
            smooth_ratio = .2
            ):
        BaseAnalyzer.__init__(self)
        self.initial_k = 60 / initial_bpm
        self.current_b = time.time()
        self.current_k = self.initial_k
        self.expected_b = None
        self.expected_k = None
        self.beat_count = 0
        self.rec_time = rec_time
        self.smooth_ratio = smooth_ratio

    def register_recorder(self,recorder):
        BaseAnalyzer.register_recorder(self,recorder)
        self.rec_size = self.sr * self.rec_time

    def run(self):
        while self.recorder.start_time is None:
            time.sleep(1)
        self.current_b = time.time()
        self.start_time = self.recorder.start_time
        while self.running.isSet():
            if len(self.audio_data) < 4 * self.sr:
                time.sleep(.5)
                self.logger.debug("The data is not enough...")
                continue
            start_samples = len(self.audio_data) - self.rec_size if len(self.audio_data) > self.rec_size else 0
            data = np.array(self.audio_data[start_samples:]).astype(np.float32)
            start_time = start_samples / self.sr
            tmpo, _beat_frames = librosa.beat.beat_track(y=data,sr = self.sr)
            beat_times = librosa.frames_to_time(_beat_frames) + start_time + self.start_time

            if len(beat_times) < 5:
                self.logger.debug("The beats count <%d> is not enough..."%len(beat_times))
                continue
            
            self.expected_k,self.expected_b = np.polyfit(range(len(beat_times)),beat_times,1)

    def block_until_next_beat(self):
        if self.expected_b is None:
            now = time.time()
            pred_i = math.ceil((now - self.current_b)/self.current_k)
            self.beat_count += pred_i
            pred_v2 = pred_i * self.current_k + self.current_b
            pred_v1 = pred_v2 - self.current_k
            pred = pred_v2
        else:
            now = time.time()
            pred_i = math.ceil((now - self.current_b)/self.current_k)
            pred_v2 = pred_i * self.current_k + self.current_b
            pred_v1 = pred_v2 - self.current_k

            exp_i = math.ceil((pred_v2 - self.expected_b)/self.expected_k)
            exp_v2 = exp_i * self.expected_k + self.expected_b
            exp_v1 = exp_v2 - self.expected_k
            if (pred_v2-exp_v1) < (exp_v2-pred_v2):
                pred = pred_v2 + (exp_v1-pred_v2)*(1-self.smooth_ratio)
                self.beat_count += pred_i 
            else:
                pred = pred_v2 + (exp_v2-pred_v2)*(1-self.smooth_ratio)
                self.beat_count += pred_i
        self.current_b = pred
        self.current_k = pred - pred_v1
        if not self.expected_k is None:
            self.current_k = self.current_k + (self.expected_k - self.current_k)*(1-self.smooth_ratio)
        if self.current_k < self.initial_k*.45:
            self.current_k *= 2
        if self.current_k > self.initial_k * 2.1:
            self.current_k /= 2
        while time.time() < pred:
            time.sleep(.01)
        return self.beat_count