import constants from "./constants";
import { getTelloIP } from "./config";
import dgram from "dgram";
import { ResponseParser, CommandType } from "./types";
import { parseCommandResponse } from "./parsers";

const client = dgram.createSocket('udp4'),
    _local = {
        state: "idle"
    }

client.on('message', (msg) => {
    _local.state = msg.toString()
});

client.bind(constants.ports.response)

const bindStateManagement = (resolve: Function, reject: Function, parser?: ResponseParser) => {
    let timeoutId = setTimeout(() => {
        _local.state = "error"
    }, 10000);
    let intervalId = setInterval(() => {
        if (isIdle())
            return
        if (isError())
            reject(_local.state)
        else {
            const rawResponse = _local.state;
            if (parser) {
                // Parse the response using the provided parser
                try {
                    const parsedResponse = parser(rawResponse);
                    resolve(parsedResponse);
                } catch (error) {
                    reject(error);
                }
            } else {
                // Return raw response if no parser is provided
                resolve(rawResponse);
            }
        }
        clearInterval(intervalId)
        clearTimeout(timeoutId)
        _local.state = "idle"
    }, 100);
}

const isIdle = () => _local.state === "idle"
const isError = () => _local.state === "error"

const transmit = (command: string) => {
    const message = Buffer.from(command)
    client.send(message, 0, message.length, constants.ports.command, getTelloIP(), (error) => {
        if (error)
            _local.state = "error"
    })
}

/**
 * Send a command to the Tello drone
 * @param command - The command to send
 * @param parser - Optional parser function to convert the response to a specific type
 * @param type - The type of command (used for determining default parser)
 * @returns Promise resolving to the parsed response
 */
const send = <T = string>(
    command: string, 
    parser?: ResponseParser<T>, 
    type: CommandType = CommandType.READ
): Promise<T> => {
    return new Promise((resolve, reject) => {
        if (!isIdle())
            reject("error")
        
        // Use provided parser or default based on command type
        const responseParser = parser || getDefaultParser<T>(type);
        
        bindStateManagement(resolve, reject, responseParser)
        transmit(command)
    })
}

/**
 * Get the default parser based on command type
 */
const getDefaultParser = <T>(type: CommandType): ResponseParser<T> => {
    switch (type) {
        case CommandType.CONTROL:
        case CommandType.SET:
            // For control and set commands, parse "ok" as true, anything else as false
            return ((response: string) => {
                return parseCommandResponse(response) as unknown as T;
            });
        case CommandType.READ:
        default:
            // For read commands, return the raw string
            return ((response: string) => response as unknown as T);
    }
}

export const exchanger = { send, _local };
export default exchanger;
