import serial
import serial.tools.list_ports
import time
import os
import subprocess

folder_name = "arduino_bowcar"
file_name = "arduino_bowcar.ino"

# 아두이노 보드의 핀 번호 기본 설정
# Default pin numbers for Arduino board
arduino_pins = '''
// Arduino pin numbers for BowCar
// 바우카를 위한 아두이노 핀 번호

// LED control pins:Digital
#define RED_LED_PIN 10
#define BLUE_LED_PIN 11

// Ultrasonic sensor pins:Digital
#define TRIG_PIN 12
#define ECHO_PIN 13

// IR sensor pins:Analog
#define IR_LEFT_PIN A6
#define IR_RIGHT_PIN A7

// Sound sensor pin:Analog
#define SOUND_SENSOR_PIN A3

// Buzzer pin:Digital
#define BUZZER_PIN 3 

// Motor control pins:Digital
#define MOTOR_LEFT_DIR_PIN 2
#define MOTOR_LEFT_PIN 5

#define MOTOR_RIGHT_DIR_PIN 4
#define MOTOR_RIGHT_PIN 6

// Button pin
// Analog
#define UP_BUTTON_PIN A0
#define DOWN_BUTTON_PIN A1
// Digital
#define LEFT_BUTTON_PIN 7
#define RIGHT_BUTTON_PIN 8

'''

# Ariduino code for Setup
# 아두이노 코드 셋업 부분
arduino_setup_code = '''
pinMode(RED_LED_PIN, OUTPUT);\n
pinMode(BLUE_LED_PIN, OUTPUT);\n
pinMode(TRIG_PIN, OUTPUT);\n
pinMode(ECHO_PIN, INPUT);\n
pinMode(IR_LEFT_PIN, INPUT);\n
pinMode(IR_RIGHT_PIN, INPUT);\n
pinMode(SOUND_SENSOR_PIN, INPUT);\n
pinMode(BUZZER_PIN, OUTPUT);\n
pinMode(MOTOR_LEFT_DIR_PIN, OUTPUT);\n
pinMode(MOTOR_LEFT_PIN, OUTPUT);\n
pinMode(MOTOR_RIGHT_DIR_PIN, OUTPUT);\n
pinMode(MOTOR_RIGHT_PIN, OUTPUT);\n
pinMode(UP_BUTTON_PIN, INPUT);\n
pinMode(DOWN_BUTTON_PIN, INPUT);\n
pinMode(LEFT_BUTTON_PIN, INPUT);\n
pinMode(RIGHT_BUTTON_PIN, INPUT);\n
'''

# Arduino code for Loop
# 아두이노 코드 루프 부분
arduino_loop_code = ""

class BowCar:
    """
    BowCar class for controlling a bowcar via serial communication.
    바우카를 제어하기 위한 BowCar 클래스입니다.
    """
    def __init__(self):
        # BowCar 객체가 생성될 때 시리얼 연결만 시도하도록 단순화
        self.port = self._find_arduino_port()
        self.connection = None
        if self.port:
            try:
                self.connection = serial.Serial(self.port, 9600, timeout=1)
                print(f'connected! 연결 성공! (port : {self.port})')
                time.sleep(2)
            except serial.SerialException:
                print(f'Failed to connect! 연결 실패! (port : {self.port})')

    def generate_arduino_file(self):
        global arduino_setup_code, arduino_loop_code, arduino_pins

        """지금까지 누적된 코드를 합쳐 최종 .ino 파일을 생성합니다."""
        print("최종 아두이노 스케치 파일을 생성합니다...")

        # 1. 최종 코드 조립
        full_code = (
            "// Auto-generated by BowCar Python module\n\n"
            + arduino_pins + "\n"
            + "void setup() {\n"
            + "  Serial.begin(9600);\n" # 시리얼 통신은 기본으로 추가
            + arduino_setup_code 
            + "}\n\n"
            + "void loop() {\n"
            + arduino_loop_code 
            + "}\n"
        )

        # 2. 폴더 생성 및 파일 쓰기
        try:
            os.makedirs(folder_name, exist_ok=True)
            full_path = os.path.join(".",folder_name, file_name)
            with open(full_path, 'w', encoding='utf-8') as f:
                f.write(full_code)
            print(f"'{full_path}' 파일 생성 완료!")
            """
            Upload the generated Arduino code to the board using arduino-cli.
            생성된 아두이노 코드를 arduino-cli를 사용하여 보드에 업로드합니다.
            """
            command: list[str] = [
                'arduino-cli', 'upload', 
                '--port', str(self.port),
                '--fqbn', 'arduino:avr:uno',  # 보드 유형을 지정합니다. (예: Arduino Uno)
                full_path
            ]
            print("업로드 명령어:", ' '.join(command))  # 업로드 명령어 출력
            print("코드 업로드 중... Uploading code...")
            try:
                # arduino-cli 명령어 실행
                result = subprocess.run(
                    command,
                    check=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                )
                print("코드 업로드 성공! Code upload successful!")
                print(result.stdout.decode('utf-8'))  # 업로드 성공 메시지 출력
            except subprocess.CalledProcessError as e:
                print(f"업로드 실패: {e}")
        except OSError as e:
            print(f"파일 생성 중 오류 발생: {e}")

    def log_arduino_code(self, code: str):
        """
        Log the Arduino code to the file.
        아두이노 코드를 파일에 기록합니다.
        """
        full_path = os.path.join(folder_name, file_name)
        with open(full_path, 'a') as f:
            f.write(code + '\n')
        print(f'Logged code to {file_name} 파일에 코드를 기록했습니다')
        

    def _find_arduino_port(self):
        ports = serial.tools.list_ports.comports()
        for port in ports:
            if 'Arduino' in port.description or 'CH340' in port.description:
                return port.device
        print('No Arduino found! 아두이노를 찾을 수 없습니다.')
        return None
    
    def send_command(self, command: str):
        """
        Send a command to the bowcar.
        바우카에 명령을 전송합니다.
        """
        if self.connection and self.connection.is_open:
            full_command = command + '\n'
            self.connection.write(full_command.encode('utf-8'))
        else:
            print('Connection is not open! 연결 되지 않아 명령을 보낼 수 없습니다.')
    


    def close(self):
        """
        Close the serial connection.
        시리얼 연결을 닫습니다.
        """
        if self.connection:
            self.connection.close()
            print('Connection closed! 연결이 닫혔습니다.')
        else:
            print('No connection to close! 닫을 연결이 없습니다.')
        
    def red_on(self):
        global arduino_loop_code
        self.send_command('lrn')
        arduino_loop_code += "digitalWrite(RED_LED_PIN, HIGH);\n"

    def red_off(self):
        global arduino_loop_code
        self.send_command('lrf')
        arduino_loop_code += "digitalWrite(RED_LED_PIN, LOW);\n"

    def blue_on(self):
        global arduino_loop_code
        self.send_command('lbn')
        arduino_loop_code += "digitalWrite(BLUE_LED_PIN, HIGH);\n"

    def blue_off(self):
        global arduino_loop_code
        self.send_command('lbf')
        arduino_loop_code += "digitalWrite(BLUE_LED_PIN, LOW);\n"

    def all_light_on(self):
        global arduino_loop_code
        self.send_command('lan')
        arduino_loop_code += "digitalWrite(RED_LED_PIN, HIGH);\n"
        arduino_loop_code += "digitalWrite(BLUE_LED_PIN, HIGH);\n"

    def all_light_off(self):
        global arduino_loop_code
        self.send_command('laf')
        arduino_loop_code += "digitalWrite(RED_LED_PIN, LOW);\n"
        arduino_loop_code += "digitalWrite(BLUE_LED_PIN, LOW);\n"
    
    def delay(self, ms: int):
        """
        Delay for a specified number of milliseconds.
        지정된 밀리초 동안 지연합니다.
        """
        global arduino_loop_code
        time.sleep(ms/1000) # Python의 sleep은 초 단위이므로 ms를 1000으로 나눔
        arduino_loop_code += f"delay({ms});\n"
