import os
import sys
import socket
import platform
import time
import RPi.GPIO as gpio
from array import *
from threading import Thread
from multiprocessing import Process
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

# В файле stepper_controller.py
from client_handler import onClient, get_setting_client

from led_controller import errorindicator

global thisstep

# from led_controller import *

# Здесь определяются функции и классы для управления шаговым двигателем

def initialize_gpio_pins(DIR_Init, STEP_Init, ENA_Init, HALL_Init, SPR_Init, SYSCOF_Init, MAX_SPEED_DELAY_small_dist_Init, START_DELAY_small_dist_Init, FIN_DELAY_small_dist_Init, MAX_SPEED_DELAY_long_dist_Init, START_DELAY_long_dist_Init, FIN_DELAY_long_dist_Init, CW_Init, CCW_Init, MICROSTEP_Init, FACTOR_Init, DEGSTEP_Init, thisstep_Init = 0):
    global SPR, SYSCOF, DIR, STEP, ENA, HALL, MAX_SPEED_DELAY_small_dist, START_DELAY_small_dist, FIN_DELAY_small_dist, MAX_SPEED_DELAY_long_dist, START_DELAY_long_dist, FIN_DELAY_long_dist, CW, CCW, MICROSTEP, FACTOR, DEGSTEP, thisstep
    SPR = SPR_Init
    SYSCOF = SYSCOF_Init
    DIR = DIR_Init
    STEP = STEP_Init
    ENA = ENA_Init
    HALL = HALL_Init
    MAX_SPEED_DELAY_small_dist = MAX_SPEED_DELAY_small_dist_Init
    FIN_DELAY_small_dist = FIN_DELAY_small_dist_Init
    MAX_SPEED_DELAY_long_dist = MAX_SPEED_DELAY_long_dist_Init 
    START_DELAY_long_dist = START_DELAY_long_dist_Init
    FIN_DELAY_long_dist = FIN_DELAY_long_dist_Init
    START_DELAY_small_dist = START_DELAY_small_dist_Init
    CW = CW_Init
    CCW = CCW_Init
    MICROSTEP = MICROSTEP_Init
    FACTOR = FACTOR_Init
    DEGSTEP = DEGSTEP_Init
    thisstep = thisstep_Init
    gpio.setmode(gpio.BCM)
    gpio.setwarnings(False)
    gpio.setup(DIR, gpio.OUT)
    gpio.setup(STEP, gpio.OUT)
    gpio.setup(ENA, gpio.OUT)
    gpio.setup(HALL, gpio.IN)
    gpio.output(ENA, gpio.HIGH)

def autotest(): # Калибровка шагового двигателя и датчика начального положения
    global Flag_LoopingVideoPlayer, thisstep, acceleration, steps_deg, delay, deceleration, SPR, SYSCOF, DIR, STEP, ENA, HALL, MAX_SPEED_DELAY_small_dist, START_DELAY_small_dist, FIN_DELAY_small_dist, MAX_SPEED_DELAY_long_dist, START_DELAY_long_dist, FIN_DELAY_long_dist, CW, CCW, MICROSTEP, FACTOR, DEGSTEP
    
    try:

        print("Запущена калибровка\n")
        onClient('Запущена калибровка')
        gpio.output(ENA,gpio.LOW)
        time.sleep(1)
        steps_deg = 3*SPR*SYSCOF
        acceleration = (MAX_SPEED_DELAY_small_dist - START_DELAY_small_dist) / steps_deg
        gpio.output(DIR,CW)
        for i in range(int(steps_deg*0.2)):
            delay = START_DELAY_small_dist + acceleration * i
            gpio.output(STEP, gpio.HIGH)
            time.sleep(delay)
            gpio.output(STEP, gpio.LOW)
            time.sleep(delay)
        for i in range(int(steps_deg)):
            #delay = start_delay + acceleration * i
            gpio.output(STEP, gpio.HIGH)
            time.sleep(MAX_SPEED_DELAY_small_dist)
            gpio.output(STEP, gpio.LOW)
            time.sleep(MAX_SPEED_DELAY_small_dist)
            if(gpio.input(HALL) == 0):

                gpio.output(STEP, gpio.LOW)
                print("Датчик подключен\n")
                gpio.output(ENA,gpio.HIGH)
                Flag_LoopingVideoPlayer = True
                thisstep = 0
                break
        else:
            time.sleep(5)
            gpio.output(DIR,CW)
            for i in range(int(steps_deg*0.2)):
                delay = START_DELAY_small_dist + acceleration * i
                gpio.output(STEP, gpio.HIGH)
                time.sleep(delay)
                gpio.output(STEP, gpio.LOW)
                time.sleep(delay)

            for i in range(int(steps_deg)):
                #delay = start_delay + acceleration * i
                gpio.output(STEP, gpio.HIGH)
                time.sleep(MAX_SPEED_DELAY_small_dist)
                gpio.output(STEP, gpio.LOW)
                time.sleep(MAX_SPEED_DELAY_small_dist)
                if(gpio.input(HALL) == 0):

                    print("Датчик подключен\n")
                    thisstep = 0
                    Flag_LoopingVideoPlayer = True
                    gpio.output(ENA,gpio.HIGH)
                    break
                

            print("Двигатель или датчик не подключен\n")
            Flag_LoopingVideoPlayer = True
            onClient("Двигатель или датчик не подключен\n")
            # gpio.output(ENA,gpio.HIGH)
            errorindicator()
    
        time.sleep(2)
        #steppertest()
        gpio.output(ENA,gpio.HIGH)

        return Flag_LoopingVideoPlayer
    
    except RuntimeError:

        time.sleep(3)
        gpio.cleanup()

        os.execv(sys.executable, [sys.executable] + sys.argv)
        #FlagReboot = True
    finally:
        gpio.output(ENA, gpio.HIGH)  # Гарантированно выключаем двигатель
        gpio.output(STEP, gpio.HIGH)
        return True

def steppertest(): # Калибровка шагового двигателя

    time.sleep(1)
    for x in range(SPR*SYSCOF//2):
        gpio.output(DIR,CCW)
        gpio.movement()
    time.sleep(3)
    if(gpio.input(HALL) == 1):
        for x in range(SPR*SYSCOF//2):
            gpio.output(DIR,CW)
            gpio.movement()
        time.sleep(3)
        if(gpio.input(HALL) == 0):
            # onClient("Двигатель подключен. Найдена нулевая точка. Калибровка окончена")
            print("Двигатель подключен. Найдена нулевая точка. Калибровка окончена\n")
        else:
            if(gpio.input(HALL) == 1):
                onClient("Двигатель не подключен")
                print("Двигатель не подключен\n")
                gpio.output(ENA,gpio.HIGH)
                errorindicator()
    else:
        if(gpio.input(HALL) == 0):
            onClient("Двигатель не подключен")
            print("Двигатель не подключен\n")
            gpio.output(ENA,gpio.HIGH)
            errorindicator()


def steppermovement(necessarystep, flag_onClient, thisstep_Init):
    global thisstep
    thisstep = thisstep_Init

    try:
        print(f'necessarystep____________________________________________{necessarystep}')
        print(f'thisstep____________________________________________{thisstep}')
        if flag_onClient:
            onClient("Запущен шаговый двигатель")

        print("Запуск шагового двигателя...")
        gpio.output(ENA, gpio.LOW)
        time.sleep(0.1)  # Задержка для стабилизации сигнала перед началом работы

        stepdifference = necessarystep - thisstep
        print(f'stepdifference____________________________________________{stepdifference}')
        if stepdifference == 0:
            print("Нет необходимости в движении.")
            # gpio.output(ENA, gpio.HIGH)
            return True

        # Определение направления движения и корректировка шагов, если нужно обернуться через 0
        direction = CW if stepdifference > 0 else CCW
        if abs(stepdifference) >= SPR / 2:
            direction = CCW if stepdifference > 0 else CW
            stepdifference = SPR - abs(stepdifference)
            print(f'stepdifference____________________________________________{stepdifference}')
        
        print(f'direction____________________________________________{direction}')

        steps_deg = abs(stepdifference) * SYSCOF  # Вычисляем общее количество шагов
        print(f"Движение {'вперед' if direction == CW else 'назад'} на {steps_deg} шагов")

        if necessarystep == 0:
            return_start()
        else:
            gpio.output(DIR, direction)
            time.sleep(0.1)  # Пауза после смены направления для устранения дребезга
            stepper_deg(steps_deg, stepdifference)
        
        thisstep = necessarystep
        degrees = (thisstep / SPR) * 360  # Вычисление угла в градусах на основе текущего шага
        print(f"Новое положение: {thisstep} шагов, что соответствует {degrees:.2f} градусам")

        if flag_onClient:
            onClient("Шаговый двигатель закончил движение")

        # gpio.output(ENA, gpio.HIGH)
        time.sleep(0.1)  # Кратковременная пауза после завершения для обеспечения полной остановки
        
    finally:
        gpio.output(ENA, gpio.HIGH)  # Гарантированно выключаем двигатель
        gpio.output(STEP, gpio.HIGH)
        onClient("Шаговый двигатель полностью остановлен")
        print("Двигатель выключен")
        return True
# Функция для отображения прогресс-бара
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█', print_end="\r"):
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filled_length = int(length * iteration // total)
    bar = fill * filled_length + '-' * (length - filled_length)
    print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)
    # Print New Line on Complete
    if iteration == total:
        print()

def stepper_deg(steps_deg, stepdifference):  # Движение шагового двигателя
    global setting_client, thisstep 
    print(f'steps_deg_______________________________________________{steps_deg}')
    print(f'stepdifference_______________________________________________{stepdifference}')

    if steps_deg == 0:
        return

    def move_step(delay):
        gpio.output(STEP, gpio.HIGH)
        time.sleep(delay)
        gpio.output(STEP, gpio.LOW)
        time.sleep(delay)

    # Функция для плавного старта, максимальной скорости и плавной остановки
    def move_with_acceleration(steps, start_delay, max_speed_delay, final_delay, acceleration_phase, deceleration_phase):
        global setting_client, thisstep
    
        i = 0
        acceleration = (max_speed_delay - start_delay) / steps
        deceleration = -(max_speed_delay - final_delay) / steps
        
        # Плавный старт
        while i < int(steps * acceleration_phase):
            setting_client = get_setting_client()  # Обновляем переменную на каждой итерации
            if len(setting_client) >= 2:
                thisstep += i
                print(f'Interrupted at step: {thisstep}')
                return
            delay = start_delay + acceleration * i
            move_step(delay)
            i += 1
            print_progress_bar(i, steps, prefix='Progress:', suffix='Complete', length=50)
            print(f'Current step: {i}, Delay: {delay}')

        # Движение на максимальной скорости
        while i < int(steps * (1 - deceleration_phase)):
            setting_client = get_setting_client()  # Обновляем переменную на каждой итерации
            if len(setting_client) >= 2:
                thisstep += i
                print(f'Interrupted at step: {thisstep}')
                return
            move_step(max_speed_delay)
            i += 1
            print_progress_bar(i, steps, prefix='Progress:', suffix='Complete', length=50)
            print(f'Current step: {i}, Delay: {max_speed_delay}')

        # Плавная остановка
        while i < steps:
            setting_client = get_setting_client()  # Обновляем переменную на каждой итерации
            if len(setting_client) >= 2:
                thisstep += i
                print(f'Interrupted at step: {thisstep}')
                return
            delay = max_speed_delay + deceleration * (i - int(steps * (1 - deceleration_phase)))
            move_step(delay)
            i += 1
            print_progress_bar(i, steps, prefix='Progress:', suffix='Complete', length=50)
            print(f'Current step: {i}, Delay: {delay}')

        # Обновляем переменную thisstep в конце движения
        thisstep += steps
        print(f'Final step: {thisstep}')

    if 0 < abs(stepdifference) <= 30:  # Необходимо пройти ровно или меньше 30 градусов
        move_with_acceleration(steps_deg, START_DELAY_small_dist, MAX_SPEED_DELAY_small_dist, FIN_DELAY_small_dist, 0.2, 0.2)
    elif 30 < abs(stepdifference) < 360:  # Необходимо пройти больше 30 градусов
        move_with_acceleration(steps_deg, START_DELAY_long_dist, MAX_SPEED_DELAY_long_dist, FIN_DELAY_long_dist, 0.2, 0.2)

def stepperJustGo(): 
    global Flag_JustGo, thisstep, acceleration, steps_deg, delay, deceleration, SPR, SYSCOF, DIR, STEP, ENA, HALL, MAX_SPEED_DELAY_small_dist, START_DELAY_small_dist, FIN_DELAY_small_dist, MAX_SPEED_DELAY_long_dist, START_DELAY_long_dist, FIN_DELAY_long_dist, CW, CCW, MICROSTEP, FACTOR, DEGSTEP
    
    thisstep = 0  # Обнуляем счетчик шагов перед началом движения
    
    try:
        onClient("Запущен шаговый двигатель в режиме свободного движения")
        print("Шаговый двигатель запущен в свободном движении")
        gpio.output(ENA, gpio.LOW)  # Активируем двигатель
        time.sleep(0.1)  # Пауза для стабилизации после активации

        steps_deg = 3 * SPR * SYSCOF  # Вычисляем количество шагов для одного полного цикла
        acceleration = (MAX_SPEED_DELAY_small_dist - START_DELAY_small_dist) / steps_deg
        gpio.output(DIR, CW)  # Устанавливаем направление движения
        
        while Flag_JustGo:  # Пока флаг JustGo активен
            for i in range(int(steps_deg * 0.2)):
                delay = START_DELAY_small_dist + i * acceleration
                gpio.output(STEP, gpio.HIGH)
                time.sleep(delay)
                gpio.output(STEP, gpio.LOW)
                time.sleep(delay)
                thisstep += 1  # Увеличиваем счетчик шагов
                if not Flag_JustGo:
                    # Хардкод для возврата с одной и той же стороны!
                    thisstep = 190
                    #
                    break 

            for i in range(int(steps_deg)):
                gpio.output(STEP, gpio.HIGH)
                time.sleep(MAX_SPEED_DELAY_small_dist)
                gpio.output(STEP, gpio.LOW)
                time.sleep(MAX_SPEED_DELAY_small_dist)
                thisstep += 1  # Увеличиваем счетчик шагов
                if not Flag_JustGo:
                    # Хардкод для возврата с одной и той же стороны!
                    thisstep = 190
                    #
                    break
        
        onClient("Шаговый двигатель остановлен после свободного движения")
        print("Свободное движение двигателя завершено")

    except RuntimeError as e:
        print(f"Ошибка выполнения: {e}")
        time.sleep(1)
        gpio.cleanup()
        os.execv(sys.executable, [sys.executable] + sys.argv)
    finally:
        # gpio.output(ENA, gpio.HIGH)  # Гарантированно выключаем двигатель
        gpio.output(STEP, gpio.LOW)
        onClient("Шаговый двигатель полностью остановлен")
        print("Двигатель выключен")
        return Flag_JustGo

def set_Flag_JustGo(Flag_JustGo_Init):
    global Flag_JustGo
    Flag_JustGo = Flag_JustGo_Init

def get_Flag_JustGo():
    global Flag_JustGo
    return Flag_JustGo

def return_start():
    global flag_onClient, thisstep, SPR

    try:
        onClient("Запущен шаговый двигатель")
        print('Попытка возврата лопасти в начальное положение...')

        gpio.output(ENA, gpio.LOW)
        time.sleep(0.1)  # Кратковременная пауза для стабилизации состояния

        # Определение направления на основе текущего положения
        direction = CCW if thisstep <= SPR / 2 else CW
        gpio.output(DIR, direction)

        first_exit = None
        second_entry = None

        while True:
            gpio.output(STEP, gpio.HIGH)
            time.sleep(START_DELAY_small_dist)
            gpio.output(STEP, gpio.LOW)
            time.sleep(START_DELAY_small_dist)

            hall_state = gpio.input(HALL)
            if hall_state == 0:  # Датчик Холла активирован
                if first_exit is None:
                    first_exit = thisstep
                else:
                    second_entry = thisstep
                    break  # Завершаем цикл после второго прохода через датчик

            # Обновляем текущую позицию шага
            thisstep += 1 if direction == CW else -1
    finally:
        # gpio.output(ENA, gpio.HIGH)  # Гарантированно выключаем двигатель
        gpio.output(STEP, gpio.LOW)
        onClient("Шаговый двигатель полностью остановлен")
        print("Двигатель выключен")
    

    # Рассчитываем среднюю точку
    if first_exit is not None and second_entry is not None:
        middle_point = (first_exit + second_entry) // 2
        steps_to_middle = abs(thisstep - middle_point)
        direction_to_middle = CW if thisstep < middle_point else CCW
        gpio.output(DIR, direction_to_middle)

        # Точное позиционирование до средней точки
        for _ in range(steps_to_middle):
            gpio.output(STEP, gpio.HIGH)
            time.sleep(0.005)  # Уменьшенная задержка для более точного позиционирования
            gpio.output(STEP, gpio.LOW)
            time.sleep(0.005)

    # gpio.output(ENA, gpio.HIGH)
    thisstep = 0  # Сохраняем новую позицию как текущую
    onClient("Шаговый двигатель завершил движение в точку между полями датчика Холла.")
    print(f'Лопасть точно настроена и достигла центральной точки над датчиком Холла.')
    return True

def get_thisstep():
    global thisstep
    return thisstep