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.LOW)
        gpio.output(STEP, gpio.LOW)
        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.LOW)
        gpio.output(STEP, gpio.LOW)
        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.LOW)
        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, CW, CCW, START_DELAY_small_dist

    try:
        onClient("Запущен шаговый двигатель")
        print('Попытка возврата лопасти в начальное положение...')

        #gpio.output(ENA, gpio.LOW)
        time.sleep(0.1)  # Кратковременная пауза для стабилизации состояния

        # Определение направления на основе текущего положения
        direction = CW if thisstep <= SPR / 2 else CCW
        gpio.output(DIR, direction)
        print(f"Начальное направление: {'CW' if direction == CW else 'CCW'}")

        hall_detected = False
        steps_to_pass = 10000  # Количество шагов для прохождения за датчик Холла

        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 and not hall_detected:  # Датчик Холла активирован в первый раз
                hall_detected = True
                print("Датчик Холла активирован первый раз")
                if direction == CCW:
                    print(f"Движение на {steps_to_pass} шагов мимо датчика Холла")
                    # Продолжаем движение еще на 100 шагов
                    for i in range(steps_to_pass):
                        gpio.output(STEP, gpio.HIGH)
                        time.sleep(START_DELAY_small_dist)
                        gpio.output(STEP, gpio.LOW)
                        time.sleep(START_DELAY_small_dist)
                        print(f"Проход мимо датчика Холла, шаг {i + 1} из {steps_to_pass}")
                    time.sleep(0.6)
                    # Меняем направление и возвращаемся к датчику Холла
                    direction = CW
                    gpio.output(DIR, direction)
                    print("Изменение направления на CW")
                    hall_detected = False  # Сбрасываем флаг для обнаружения Холла при обратном движении

            if hall_detected and gpio.input(HALL) == 0:  # Датчик Холла снова активирован
                print("Датчик Холла активирован второй раз")
                break  # Завершаем цикл после второго прохода через датчик

            # Обновляем текущую позицию шага
            thisstep += 1 if direction == CW else -1
            # print(f"Текущий шаг: {thisstep}, направление: {'CW' if direction == CW else 'CCW'}")

        # Останавливаем двигатель в нулевой точке
        #gpio.output(STEP, gpio.LOW)
        thisstep = 0  # Сохраняем новую позицию как текущую
        onClient("Шаговый двигатель завершил движение в нулевую точку.")
        print('Лопасть точно настроена и достигла нулевой точки над датчиком Холла.')

    finally:
        gpio.output(ENA, gpio.LOW)
        gpio.output(STEP, gpio.LOW)
        onClient("Шаговый двигатель полностью остановлен")
        print("Двигатель выключен")
        return True


def get_thisstep():
    global thisstep
    return thisstep
