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
# from client_handler import CW, DIR, ENA, HALL, SPR, SYSCOF, FIN_DELAY_long_dist, MAX_SPEED_DELAY_small_dist, \
#     START_DELAY_small_dist, FIN_DELAY_small_dist, MAX_SPEED_DELAY_long_dist, START_DELAY_long_dist, STEP, onClient, \
#     autotest_client, connected_socket
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 stepper(steps_deg, stepdifference):
    # Уточнение и оптимизация временных задержек и управления шагами
    if steps_deg == 0:
        return

    start_delay, max_speed_delay, fin_delay = determine_delays(stepdifference)
    steps_ratio = calculate_step_ratio(steps_deg)
    segments = calculate_segments(steps_ratio)
    accelerations = calculate_accelerations(segments, start_delay, max_speed_delay, fin_delay)
    perform_movement(segments, accelerations)

def determine_delays(stepdifference):
    # Определение задержек на основе разности шагов
    if abs(stepdifference) <= 30:
        return START_DELAY_small_dist, MAX_SPEED_DELAY_small_dist, FIN_DELAY_small_dist
    else:
        return START_DELAY_long_dist, MAX_SPEED_DELAY_long_dist, FIN_DELAY_long_dist

def calculate_step_ratio(steps_deg):
    # Вычисление соотношения шагов
    return steps_deg / FACTOR / MICROSTEP * DEGSTEP

def calculate_segments(steps_ratio):
    # Расчет количества шагов для каждой фазы движения
    segment_ratios = [0.1, 0.2, 0.4, 0.2, 0.1]
    return [int(steps_ratio * ratio) for ratio in segment_ratios]

def calculate_accelerations(segments, start_delay, max_speed_delay, fin_delay):
    # Расчет ускорений и замедлений
    return [
        (max_speed_delay - start_delay) / segments[1],
        -(max_speed_delay - fin_delay) / segments[3]
    ]

def perform_movement(segments, accelerations):
    # Выполнение движения
    speeds = [START_DELAY_small_dist, START_DELAY_small_dist, MAX_SPEED_DELAY_small_dist, MAX_SPEED_DELAY_small_dist, FIN_DELAY_small_dist]
    for i, segment in enumerate(segments):
        for step in range(segment):
            delay = calculate_delay(i, step, speeds, accelerations)
            perform_step(delay)

def calculate_delay(i, step, speeds, accelerations):
    # Вычисление задержки для каждого шага
    delay = speeds[i]
    if i in [1, 3]:  # Этапы ускорения и замедления
        delay += accelerations[i//3] * step
    return delay

def perform_step(delay):
    gpio.output(STEP, gpio.HIGH)
    time.sleep(delay / 2)  # Половина времени на активацию
    gpio.output(STEP, gpio.LOW)
    time.sleep(delay / 2)  # Половина времени на паузу

def precise_sleep(duration):
    """ Осуществляет точную задержку исполнения на заданное время с использованием `time.sleep()` для уменьшения загрузки CPU. """
    end_time = time.perf_counter() + duration
    while time.perf_counter() < end_time:
        # Используем time.sleep() для минимизации нагрузки на CPU
        time_to_sleep = end_time - time.perf_counter()
        if time_to_sleep > 0:
            time.sleep(time_to_sleep)

#################

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")
                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
                    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

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:
        if flag_onClient:
            onClient("Запущен шаговый двигатель")

        print("Запуск шагового двигателя...")
        gpio.output(ENA, gpio.LOW)
        time.sleep(0.1)  # Задержка для стабилизации сигнала перед началом работы

        stepdifference = necessarystep - thisstep
        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)

        gpio.output(DIR, direction)
        time.sleep(0.1)  # Пауза после смены направления для устранения дребезга

        steps_deg = abs(stepdifference) * SYSCOF  # Вычисляем общее количество шагов
        print(f"Движение {'вперед' if direction == CW else 'назад'} на {steps_deg} шагов")

        # Перемещение двигателя на заданное количество шагов
        for _ in range(int(steps_deg)):
            perform_step(START_DELAY_small_dist)  # Выполнение каждого шага

        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.LOW)
        onClient("Шаговый двигатель полностью остановлен")
        print("Двигатель выключен")
        return True



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