import WebSocket from 'ws';
import ConfigParser from 'configparser';
import OpusFileStream from './opus-file-stream.js';
import Redis  from 'ioredis';
import { exec, spawn } from 'child_process';
import fs from 'fs';


import OpusScript from 'opusscript';
let opusStreamFile = fs.createWriteStream('audio_stream.opus');

let { SYRUS4G_SYSTEM_REDIS_PW } = process.env;

const version = '1.0.7';

// Create a new Opus decoder
const sampleRate = 24000;  // Opus typically uses a sample rate of 48kHz
const channels = 1;        // Mono audio (you can use 2 for stereo)
const frameSize = 960;     // 20ms frame size

const opusDecoder = new OpusScript(sampleRate, channels, OpusScript.Application.VOIP, { wasm: false });
let audioBuffer = [];
let ws = null;


// Subscribe to Redis
let redisClient = null; 
let queueRedisClient = null;

// Read configuration file
const config = new ConfigParser();
config.read(`${process.env.APP_DATA_FOLDER}/configuration.zello.conf`);
if (config.sections().length === 0) {
    console.error('Configuration file not found!');
    process.exit(1);
}
const dispath_channel = config.get('zello', 'channel');


console.log(`\n`);
console.log(`*****************************`);
console.log(`Starting Zello App v${version} \n`);

let state = 'init';

let login = {
    username: '',
    password: '',
    channels: [],
}

let guards = {
    audioConnected: false,
    internetConnected: false,
    zelloConnected: false,
    sending: false,
    receiving: false,
};

let audioFile = {
    name: 'encoded_audio_0.opus',
    number: 0,
};


let timeoutId = null;
let pingCounter = 0;
let recordingPid = null;
let statusCheckCounter = 60;

/** Functions **/

// Set configuration file change detection
fs.watchFile(`${process.env.APP_DATA_FOLDER}/configuration.zello.conf`, { interval: 10000 }, (curr, prev) => {
    console.log('Configuration file changed');
    console.log(`Reloading...`);
    process.exit(1);
    //config.read(`${process.env.APP_DATA_FOLDER}/configuration.zello.conf`);
});


const sleep = ms => new Promise(r => setTimeout(r, ms));

// Function to decode accumulated Opus packets
function decodePackets(packets) {
    packets.forEach((opusFrame) => {
      try {
        // Decode the Opus-encoded audio frame
        const decodedPCM = opusDecoder.decode(opusFrame, frameSize);
        
        // Save decoded PCM to file or process further (in this case, we're saving it)
        savePCMToFile(decodedPCM);
        
      } catch (err) {
        console.error(`Failed to decode packet:`, err);
      }
    });
}
  
// Function to save decoded PCM data to a file
function savePCMToFile(decodedPCM) {
    // Convert the PCM data to a Buffer and append it to a file
    const pcmBuffer = Buffer.from(decodedPCM);

    fs.appendFileSync('decoded_audio.pcm', pcmBuffer);
}

// TODO: Adjust this parameters to make the conversion right
function pcmToWav(pcmFilePath, wavFilePath, sampleRate = 24000, numChannels = 1, bitsPerSample = 16) {
    // Read the PCM data from the file
    const pcmData = fs.readFileSync(pcmFilePath);
  
    const blockAlign = numChannels * bitsPerSample / 8;
    const byteRate = sampleRate * blockAlign;
    const dataSize = pcmData.length;
  
    // Create a buffer for the WAV header + PCM data
    const buffer = Buffer.alloc(44 + dataSize);
  
    // Write WAV header
    buffer.write('RIFF', 0); // ChunkID
    buffer.writeUInt32LE(36 + dataSize, 4); // ChunkSize
    buffer.write('WAVE', 8); // Format
    buffer.write('fmt ', 12); // Subchunk1ID
    buffer.writeUInt32LE(16, 16); // Subchunk1Size (PCM)
    buffer.writeUInt16LE(1, 20); // Audio format (1 = PCM)
    buffer.writeUInt16LE(numChannels, 22); // NumChannels  22
    buffer.writeUInt32LE(sampleRate, 24); // SampleRate 24
    buffer.writeUInt32LE(byteRate, 28); // ByteRate
    buffer.writeUInt16LE(blockAlign, 32); // BlockAlign
    buffer.writeUInt16LE(bitsPerSample, 34); // BitsPerSample 34
    buffer.write('data', 36); // Subchunk2ID
    buffer.writeUInt32LE(dataSize, 40); // Subchunk2Size
  
    // Copy PCM data after the header
    pcmData.copy(buffer, 44);
  
    // Write the result to a .wav file
    fs.writeFileSync(wavFilePath, buffer);
    console.log(`Converted ${pcmFilePath} to ${wavFilePath}`);
}


function tryParseJSON(jsonString) {
    try {
        const jsonObject = JSON.parse(jsonString);
        if (jsonObject && typeof jsonObject === "object") {
            return jsonObject;
        }
    } catch (e) {
        console.error("Error parsing JSON:", e);
    }
    return false;
}

function execShellCommand(cmd, onCompleteCb) {
    exec(cmd, (error, stdout, stderr) => {
        if (error) {
            console.error("execShellCommand error:", error);
            return;
        } else if (stderr) {
            console.error("execShellCommand stderr:", stderr);
            return;
        }

        console.log("Command Excecuted:", stdout);
        if (onCompleteCb) {
            onCompleteCb(stdout);
        }
    });
}

function startRecording() {
    try {
        recordingPid = spawn('arecord', ['-D', 'plughw:0,0', '-f', 'cd', '-r', '16000', '-c', '1', '-t', 'wav', '|', 'opusenc', '--bitrate', '16', '-', 'encoded_audio_0.opus'], {    
            stdio: 'inherit',
            shell: true
        }, (error) => {
            if (error) {
                console.error('Failed to start the recording process:', error);
            }
        });

        if (!recordingPid || !recordingPid.pid) {
            console.error('Failed to start the recording process.');
            return;
        }   

        console.log('Recording Process PID:', recordingPid.pid);
    } catch (error) {
        console.error('Failed to start the recording process:', error);
    }
}

function getCurrentTimeMs() {
    const now = new Date();
    return now.getTime();
}

function sendAudioPacket(ws, packet, startTsMs, timeStreamingMs, onCompleteCallback) {
    //console.log('sendAudioPacket', packet, startTsMs, timeStreamingMs);
    const timeElapsedMs = getCurrentTimeMs() - startTsMs;
    const timeToWaitMs = timeStreamingMs - timeElapsedMs;

    ws.send(packet);
    if (timeToWaitMs < 1) {
        //setTimeout(onCompleteCallback, timeToWaitMs);
        return onCompleteCallback();
    }
    setTimeout(onCompleteCallback, timeToWaitMs);
}

function generateAudioPacket(data, streamId, packetId) {
    //console.log('generateAudioPacket', streamId, packetId);
    let packet = new Uint8Array(data.length + 9);
    packet[0] = 1;

    let id = streamId;
    for (let i = 4; i > 0; i--) {
        packet[i] = parseInt(id & 0xff, 10);
        id = parseInt(id / 0x100, 10);
    }

    id = packetId;
    for (let i = 8; i > 4; i--) {
        packet[i] = parseInt(id & 0xff, 10);
        id = parseInt(id / 0x100, 10);
    }
    packet.set(data, 9);
    return packet;
}

function sendAudioStream(ws, streamId, opusStream, onCompleteCallback) {
    const startTsMs = getCurrentTimeMs();
    let timeStreamingMs = 0;
    let packetId = 0;

    console.log('sendAudioStream', streamId);

    const streamNextPacket = () => {

        try {
            opusStream.getNextOpusPacket(null, false, (data) => {
                if (!data) {
                    console.log('Stream ended');
                    return onCompleteCallback(true);
                }

                const packet = generateAudioPacket(data, streamId, packetId);
                timeStreamingMs += opusStream.packetDurationMs;
                packetId++;

                sendAudioPacket(ws, packet, startTsMs, timeStreamingMs, () => { return streamNextPacket() } );
            });
        } catch (error) {
            console.error('Stream Error:', error);
            return onCompleteCallback(false);
        }

    };

    streamNextPacket();
    // TODO: add fail callback

}

function startStream(ws, opusStream) { // , onCompleteCallback

    if (opusStream == null) {
        console.error('startStream: opusStream is null');
        return;
    }

    console.log('startStream', opusStream);
    let codedHeaderRaw = new Uint8Array(4);
    codedHeaderRaw[2] = opusStream.framesPerPacket;
    codedHeaderRaw[3] = opusStream.packetDurationMs;
    // Sample rate two bytes in little-endian
    codedHeaderRaw[0] = parseInt(opusStream.sampleRate & 0xff, 10);
    codedHeaderRaw[1] = parseInt(opusStream.sampleRate / 0x100, 10) & 0xff;
    const codedHeader = Buffer.from(codedHeaderRaw).toString('base64');

    ws.send(JSON.stringify({
        "command": "start_stream",
        "seq": 2,
        "channel": dispath_channel,
        "type": "audio",
        "codec": "opus",
        "codec_header": codedHeader,
        "packet_duration": opusStream.packetDurationMs
    }));

    const startTimeoutMs = 2000;
    timeoutId = setTimeout(() => {
        console.error('startStream timeout');
    }, startTimeoutMs, null);
}

function stopStream(ws, streamId) {
    ws.send(JSON.stringify({
        "command": "stop_stream",
        "stream_id": streamId
    }));

    //streamId = null;
}

async function getFileFromRedis() {
    // Get fileName from redis lrange

    try {
        const element = await queueRedisClient.lrange('/data/app_data/__cloud_zello-app_audio_queue', 0, 0);
        //console.log('getFileFromRedis:', element);

        // Some file found in the queue
        if (element.length > 0) {
            audioFile.name = element[0];
            audioFile.number = parseInt(audioFile.name.split('_')[2].split('.')[0]);
            //console.log('getFileFromRedis has something:', audioFile);
        } else {
            // No file in queue
            audioFile.number = 0;
            audioFile.name = `encoded_audio_${audioFile.number}.opus`;
            //console.log('getFileFromRedis no file:', audioFile);
        }

    } catch (error) {
        console.error('getFileFromRedis error:', error);
    }

    return audioFile;
}

async function setFileOnRedis() {
    // Set fileName from redis lpush
    try {
        const pendingFiles = await queueRedisClient.llen('/data/app_data/__cloud_zello-app_audio_queue');
 
        if (pendingFiles == 0) {

            audioFile.number = pendingFiles;
            audioFile.name = `encoded_audio_${pendingFiles}.opus`;
            console.log('setFileOnRedis has file:', audioFile);

        } else {
            // No file in queue
            audioFile.number = pendingFiles;
            audioFile.name = `encoded_audio_${pendingFiles}.opus`;
            console.log('setFileOnRedis no file:', audioFile);
        }
        console.log('setFileOnRedis registerNewFile:', audioFile);
    
    } catch (error) {
        console.error('setFileOnRedis error:', error);
        
    } 

    return audioFile;
}

async function checkPendingAudio() {
    // Check if there are any pending audio files in the queue
    // If there are, send them to the server
    // If there are not, do nothing
    /** 
    console.log('checkPendingAudio');

    const pendingFiles = await queueRedisClient.llen('/data/app_data/__cloud_zello-app_audio_queue');
    if (pendingFiles > 0 && guards.zelloConnected) {
        // Send audio file
        const fileToSend = await getFileFromRedis();
        console.log('checkPendingAudio theres a file waiting to be send:', fileToSend);
        new OpusFileStream(`${fileToSend.name}`, (opus_stream) => {
            startStream(ws, opus_stream);
        });
    }*/
}

function checkInternetConnection( onCompleteCallback) {

    exec('ping 8.8.8.8 -c 1', (error, stdout, stderr) => {
        if (error) {
            //console.error("execShellCommand error:", error);
            onCompleteCallback(false);
        } else if (stderr) {
            //console.error("execShellCommand stderr:", stderr);
            onCompleteCallback(false);
        } else {
            if (onCompleteCallback) {
                // Separate the result from the command on lines
                const lines = stdout.split('\n');
                if (lines[4] != undefined && lines[4].endsWith('0% packet loss') ) {
                    onCompleteCallback(true);
                } else {
                    onCompleteCallback(false);
                }
            }
        }

    });

}

function getUsernameAndPass() {
    exec('hostname', (error, stdout, stderr) => {
        if (error) {
            console.error("getUsernameAndPass error:", error);
        } else if (stderr) {
            console.error("getUsernameAndPass stderr:", stderr);
        } else {
            //console.log("getUsernameAndPass:", stdout);
            login.username = stdout.trim();
            login.password = stdout.trim().split('-')[1];
        }
    });
}

/** Function to send message from the client **/

async function subscribeToRedis() {
    console.log('Subscribing to Redis...');

    queueRedisClient = new Redis({
        port: 6379,
        host: '127.0.0.1',
    });

    redisClient = new Redis({
        port: 7480, //7480 6379
        host: '127.0.0.1',
        password: SYRUS4G_SYSTEM_REDIS_PW,
    });

    
    const selectedInput = config.get('device', 'button') || 'IN1';
    redisClient.subscribe(`interface/input/${selectedInput}`, 'bluetooth/notification/update',  (error, count) => {
        if (error) {
            console.error('Error subscribing to Redis:', error);
        } else {
            console.log('Subscribed to Redis count:', count);
        }
    });

    redisClient.on('connect', () => {
        console.log('Connected to Redis!');
    });

    redisClient.on('error', (err) => {
        console.log('Redis Error:', err);
    });

    redisClient.on('message', (channel, message) => {
        console.log('Received message from Redis:', channel, message );

        if (channel === `interface/input/${selectedInput}`) {
            
            if (message == 'true') {
                console.log('Button pressed');
                if ( state != 'recording' && guards.receiving == false) { //guards.audioConnected &&
                    guards.sending = true;
                    state = 'startRecording';
                    stateMachine('startRecording');
                } else {
                    console.log('Audio Device not connected or receiving audio');
                }
    
            } else if (message == 'false') {
                console.log('Button released');
    
                if (state == 'recording') { // guards.audioConnected && 
                    state = 'stopRecording';
                    stateMachine('stopRecording');
                } else {
                    console.log('Not recording');
                }
    
            }
        } 

        if (channel === 'bluetooth/notification/update') {
            console.log('Bluetooth Notification Update:', message);
            const msg = tryParseJSON(message);

            // Poll for audio device connection- NO POLLING AVAILABLE
            if (msg) {
                console.log('Bluetooth Notification Update:', msg);
                if (msg.paired == true && msg.connected == true) {
                    console.log('Bluetooth Device Connected');
                    guards.audioConnected = true;
    
                } else if (msg.paired == true && msg.connected == false) {
                    console.log('Bluetooth Device Disconnected');
                    guards.audioConnected = false;
    
                } else if (msg.paired == false) {
                    console.log('Bluetooth Device Unpaired');
                    guards.audioConnected = false;
    
                }
            }
        }

    });

}

function connectToServer(ws, serverConfig) {

    const zello_channel = serverConfig.get('zello', 'channel');
    const username = login.username;
    const password = login.password;
    console.log('connectToServer: Subscribed to channel [%s] as [%s]', zello_channel, username);

    /** When client connects to server **/
    ws.on('open', () => {
        console.log('Connected to websocket!');

        const message = JSON.stringify({
            "command": "logon",
            "seq": 1,
            "username": username,    //"ubuntu",            //usr_imei
            "password": password,    //"rasengan",          //imei
            "channels": [zello_channel]    //["DispatchChannel"]  // Take from configuration file
        });

        ws.send(message);
    });


    /** When client receives message from server **/
    ws.on('message', (data) => {
        //console.log('Received from server: %s %s %s', data, data[0], data[data.length - 1]);

        // Process audio or JSON data incoming from websocket
        if (data[0] === 123 && data[data.length-1] === 125  ) { // "{" and "}"

            const response = tryParseJSON(data);  //JSON.parse(data);

            console.log('ws.on message:', response);

            // Connected to Zello Server
            if (response.success && response.seq == 1) {
                console.log('Connected to Zello Server!');
                guards.zelloConnected = true;
                guards.sending = false;
                guards.receiving = false;
            }

            // Recieve Stream Flags - Receive Stream
            if ( response.stream_id && response.command == "on_stream_start" ) {

                console.log('Stream Received!');
                if ( guards.sending == false && guards.receiving == false) {
                    guards.receiving = true;
                    //opusStreamFile = fs.createWriteStream(`input_audio_stream.opus`);
                    audioBuffer = [];
                    if (fs.existsSync('decoded_audio.pcm')) {
                        fs.unlinkSync('decoded_audio.pcm');
                    }

                } else {
                    console.log('Busy stream, unable to receive');
                }


            } else if ( response.stream_id && response.command == "on_stream_stop" ) {
                console.log('Stream Ended', response);

                if (guards.sending == false && guards.receiving == true) {
                    
                    decodePackets(audioBuffer);
                    audioBuffer = [];
                    //opusStreamFile.end();
    
                    state = 'receiveAudio';

                } else {
                    console.log('Busy stream, unable to receive');
                }

            }
            
            if ( response.command == "on_dispatch_call_status" && response.status == "taken" ) {
                console.log('Call Taken dispatcherName:', response.dispatcher_name);
                guards.zelloConnected = true;
            }

            // Start Stream Upload - Audio upload authorized response with an stream_id
            if (response.success && response.stream_id) {
                console.log('Stream accepted, clear to send with id:', response.stream_id);
                clearTimeout(timeoutId);

                if (guards.sending == true && guards.receiving == false) {
                   
                    getFileFromRedis().then((fileToSend) => {
                        console.log('File to send:', fileToSend);
                        new OpusFileStream(`${fileToSend.name}`, (opus_stream) => {

                            sendAudioStream(ws, response.stream_id, opus_stream, (success) => {
                                if (!success) {
                                    console.log('Stream failed');
                                }
                                stopStream(ws, response.stream_id);
                                console.log('Stream Sendend');
                            });
                        });
                    });

                }



            }

            if (response.command == "on_channel_status") {
                //console.log('Stream Status:', response);
                if (response.status == "online") {
                    console.log('Channel status:', response.status);
                    guards.zelloConnected = true;
                }  else {
                    console.log('Channel status offline:', response.status);
                    //guards.zelloConnected = false;
                }
            }

            // Stream Sended success response
            if (response.success) {
                console.log('Stream Sended:', response);

                if (guards.sending == true && guards.receiving == false) {

                    // Remove file just sended
                    getFileFromRedis().then((fileToSend) => {
                        
                        if (fs.existsSync(fileToSend.name)) {
                            fs.unlinkSync(fileToSend.name); 
                        }

                        // Remove file from queue
                        queueRedisClient.lpop('/data/app_data/__cloud_zello-app_audio_queue');
                        
                        // Check for more pending audio
                        //checkPendingAudio();
                    });

                    // Release mutex
                    guards.sending = false;
                }

            } 
            
            if ( response.command == 'on_error' && response.error == 'kicked') {
                console.error('Stream error:', response);
            }

        } else {

            if (data.length > 9) {
                // Only the first 9 bytes are the header
                //console.log('Opus Packet:', data.length, data );
                if ( guards.sending == false  && guards.receiving == true) { 
                    const opusFrame = data.slice(9);
                    audioBuffer.push(opusFrame);
                } else {
                    console.log('Busy stream, unable to receive');
                }

            }
        }

    });

    /** When client disconnects from server **/
    ws.on('close', () => {
        if (ws.readyState === WebSocket.CLOSED) {
            console.log('WebSocket is closed');
            guards.zelloConnected = false;
        } else if (ws.readyState === WebSocket.CLOSING) {
            console.log('WebSocket is closing');
        } else if (ws.readyState === WebSocket.CONNECTING) {
            console.log('WebSocket is connecting');
        } else if (ws.readyState === WebSocket.OPEN) {
            console.log('WebSocket is open');
        } else {
            console.log('WebSocket is unknown');
        }
    
        //guards.zelloConnected = false;
        //state = 'idle';
    });

    /** When client encounters an error **/
    ws.on('error',  (err) => {
        console.log('Error: %s', err);
        guards.zelloConnected = false;
        state = 'idle';
    });

    ws.on('ping', (data) => {
        //console.log('Ping:', data);
        guards.zelloConnected = true;
        pingCounter += 1;
    });

    //ws.on('pong', (data) => { 
    //    console.log('Pong:', data);
    //    guards.zelloConnected = true;
    //});  

}

async function stateMachine(currentState) {
    switch (currentState) {
        case 'init':
            // Initialize audio device
            console.log('StateMachine: init');
            getUsernameAndPass();
            state = 'connectToZelloServer';
            break;

        case 'checkAudioDevice':
            // Connect to audio device
            // Change state to 'idle'
            console.log('StateMachine: checkAudioDevice');
            execShellCommand('apx-bt list_connected', (output) => {
                const response = tryParseJSON(output);
                if (response) {
                    console.log('checkAudioDevice routed audio:', response);
                    const key = Object.keys(response)[0];
                    if (response.key == 'connected') {
                        console.log('Audio Device Connected:', key);
                        guards.audioConnected = true;
                    } else {
                        console.log('Audio Device Disconnected');
                        guards.audioConnected = false;
                    }
                }
            });
            break;

        case 'checkInternetConnection':
            console.log('StateMachine: checkInternetConnection');
            checkInternetConnection((connected) => {
                console.log('Internet Connection:', connected, guards.zelloConnected);
                if (connected && guards.zelloConnected == false ) {
                    state = 'connectToZelloServer';
                } else if (connected && guards.zelloConnected == true) {
                    state = 'idle';
                } else if (connected == false && guards.zelloConnected == true) {
                    guards.zelloConnected = false;
                }
            });

            break;

        case 'connectToZelloServer':
            // Connect to Zello server
            console.log('StateMachine: connectToZelloServer');
            console.log('Connecting to server...');
            guards.receiving = false;
            const zello_network = config.get('zello', 'network');
            ws = new WebSocket(`wss://zellowork.io/ws/${zello_network}`);
            console.log('Connected to Network...', zello_network);
            connectToServer(ws, config);
            state = 'idle';

            break;

        case 'idle':
            // Wait for user to press a button

            if (statusCheckCounter <= 0) {

                // Check Zello Server Connection
                if(ws.readyState === WebSocket.OPEN) {
                    if (pingCounter > 0) {
                        guards.zelloConnected = true;
                    } else {
                        guards.zelloConnected = false;
                    }
                } else {
                   guards.zelloConnected = false;
                }

                // Check internet connection
                checkInternetConnection((connected) => {
                    console.log('Internet: %s, Zello: %s', connected, guards.zelloConnected);
                    if (connected && guards.zelloConnected == false) {
                        state = 'connectToZelloServer';
                    } else if (connected && guards.zelloConnected == true) {
                        state = 'idle';
                    }else if (connected == false && guards.zelloConnected == true) {
                        guards.zelloConnected = false;
                    }
                });
                
                pingCounter = 0;
               // Reset countdown
                statusCheckCounter = 60;
            }

            statusCheckCounter -= 1;
            sleep(1);
            break;

        case 'startRecording':
            // Start recording audio
            // Change state to 'transmitAudio'
            console.log('StateMachine: startRecording');
            if (!guards.sending || !guards.receiving) {
                startRecording();
                state = 'recording';
            } else {
                console.log('Busy stream, unable to record');
            }
            break;

        case 'recording':
            console.log('StateMachine: recording');
            break;

        case 'stopRecording':
            // Stop recording audio
            console.log('StateMachine: stopRecording');
            execShellCommand(`pidof arecord | xargs kill -15`);

            if (fs.existsSync(audioFile.name)) {
                queueRedisClient.rpush('/data/app_data/__cloud_zello-app_audio_queue', audioFile.name);
            }

            state = 'transmitAudio';
            break;

        case 'transmitAudio':
            // Send audio data
            // Change state to 'idle'
            console.log('StateMachine: transmitAudio');

            // Wrap audio data from file
            getFileFromRedis().then((fileToSend) => {

                if (fs.existsSync(fileToSend.name)) {
                    console.log('transmitAudio:', fileToSend);

                    new OpusFileStream(`${fileToSend.name}`, (opus_stream) => {
                        if (opus_stream == null) {
                            console.log('transmitAudio: opusStream is null, skip transmission');
                        } else {
                            startStream(ws, opus_stream);
                        }
                    });
                }
            });

            state = 'idle';
            break;

        case 'receiveAudio':
            // Receive audio data
            // Change state to 'idle'
            console.log('StateMachine: receiveAudio');

            //const pcmFilePath = 'decoded_audio.pcm';
            //const wavFilePath = 'decoded_audio.wav';
            //pcmToWav(pcmFilePath, wavFilePath);
            //fs.unlinkSync(pcmFilePath);

            if (guards.receiving == true && guards.sending == false) {
            
                execShellCommand(`aplay -f S16_LE -r 35000 -c 2  decoded_audio.pcm`); //64
                guards.receiving = false;
            }




            state = 'idle';
            break

        default:
            console.error('Unknown state:', state);
            break;
    }
}



/** State Machine **/
async function run() {
    try {

        subscribeToRedis();

        // Enter State Machine
        console.log('Entering State Machine...');

        while (true) {
            await sleep(1000);
            //console.log('State:', state);
            await stateMachine(state);
        }

    

    } catch (error) {
        console.error('Run Error:', error);
    }

}


run();