from core.utils import *
import os

'''
base discrete/takahashi PID controller
'''
class BasePID:
    def __init__(self, target, clip_min, clip_max, controller_type, kp=0, ki=0, kd=0, dt=1, Kc=0, Ti=0, Td=0, Ts=0, debug=False, type='base', swap_error_fn=False):
        self.Kp = kp # discrete pid kp
        self.Ki = ki # discrete pid ki
        self.Kd = kd # discrete pid kd
        self.T = dt # discrete pid frequency time.
        self.Ti = Ti # takahashi ti
        self.Td = Td # takahashi td
        self.Ts = Ts # takahashi ts
        self.Kc = Kc # takahashi kc
        self.target = target # pid set point, target
        self.prev_feedback = 0
        self.feedback_hist = [0, 0]
        self.output_hist = [0]
        self.error_hist = [0, 0]
        self.debug=debug
        self.clip_min = clip_min
        self.clip_max = clip_max
        self.controller_type = CONTROLLER_TYPE_DISCRETE
        self.swap_error_fn = swap_error_fn
        self.type = type

    def continuous_pid(self, feedback):
        ret =  (self.Kp * self.proportional(feedback)) + (self.Ki * self.integral(feedback)) + (self.Kd * self.derivative(feedback))
        self.feedback_hist+=[feedback]
        self.prev_feedback=feedback
        return ret

    def discrete_pid(self, feedback, debug=True):
        k1 = self.Kp + self.Ki + self.Kd
        k2 = -1 * self.Kp - 2 * self.Kd
        k3 = self.Kd
        err = self.proportional(feedback)
        ret = self.output_hist[-1] + k1 * err + k2 * self.error_hist[-1] + k3 * self.error_hist[-2]
        self.error_hist+=[err]
        self.feedback_hist+=[feedback]
        return ret

    def zero_feedback_hist(self):
        count = 0
        length = len(self.feedback_hist)
        for i in range(0,length):
            if self.feedback_hist[length-(i+1)]==0:
                count+=1
            else:
                return count
        return count

    def takahashi(self, feedback, debug=True):
        err = self.proportional(feedback)
        ret = self.output_hist[-1] + self.Kc * (self.feedback_hist[-1] - feedback + self.Ts * err/ self.Ti +  self.Td / self.Ts * (2*self.feedback_hist[-1] - feedback  - self.feedback_hist[-2]))
        self.error_hist+=[err]
        self.feedback_hist+=[feedback]
        return ret

    def pid_clipped(self, feedback, debug=True):
        pid_value = None
        if self.controller_type == CONTROLLER_TYPE_TAKAHASHI:
            pid_value = self.takahashi(feedback, debug)
        elif self.controller_type == CONTROLLER_TYPE_DISCRETE:
            pid_value = self.discrete_pid(feedback, debug)
        else:
            pid_value = self.continuous_pid(feedback)

        if pid_value <= self.clip_min:
            pid_value = self.clip_min
        if pid_value >= self.clip_max:
            pid_value =  self.clip_max

        #if self.integral(feedback) == 0 and len(self.feedback_hist) >=3 and self.feedback_hist[-1] == 0 and self.feedback_hist[-2] == 0 and self.feedback_hist[-3] == 0:
            #pid_value = 0.9**self.zero_feedback_hist()

        self.output_hist+=[pid_value]
        return pid_value

    def error(self, feedback):
        if self.swap_error_fn:
            # maintain positive proportional gains
            return self.target - feedback
        else:
            return feedback - self.target

    def proportional(self,  feedback):
        return self.error(feedback)

    def integral(self, feedback):
        return sum(self.feedback_hist[-10:]) + feedback

    def derivative(self, feedback):
        return (self.error(self.prev_feedback) - self.error(feedback)) / self.T

    def write_feedback(self, feedback_hist_file):
        if len(self.feedback_hist)==0:
            return
        buf = ''
        buf+=str(self.feedback_hist[0])
        buf+=','
        for i in self.feedback_hist[1:]:
            buf+=str(i)+','
        with open(feedback_hist_file, "w+") as f:
            f.write(buf)

    def write_fval(self, output_hist_file):
        if len(self.output_hist)==0:
            return
        buf = ''
        buf+=str(self.output_hist[0])
        buf+=','
        for i in self.output_hist[1:]:
            buf+=str(i)+','
        with open(output_hist_file, "w+") as f:
            f.write(buf)

    def write(self, feedback_hist_file='_feedback.hist', output_hist_file='_output.hist'):
        self.write_feedback('log' + os.sep + self.type+feedback_hist_file)
        self.write_fval('log'+ os.sep + self.type+output_hist_file)

    def acc(self, window=ACC_WINDOW):
        windowed_hist = [round(hist, 2) for hist in self.feedback_hist][-1*window:]
        return sum(np.array(windowed_hist)==self.target)/float(len(windowed_hist))

    def acc_percentage(self):
        return self.acc(window=0) * 100
