//
// Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
// Created Date: 2024-09-03 10:20:36
// -----
// Last Modified: 2024-11-12 06:31:09
// -----
//
//

//! Used for development purposes, where we test designs and APIs to see if they work out the way we intended.
//! Any code of value becomes an integration test.
//! This file is a "scatch-pad," and should generally be ignored.

use std::time::Duration;

use async_trait::async_trait;
use mechutil::{
    command::{MechFsmCommandTuple, MechFsmControl, MechFsmState, MechFsmStatusTuple},
    command_arg_tuple::MechFsmCommandArgTuple,
    command_fsm::{
        CommandFsmHandle, CommandFsmHandler, CommandFsmMessage, CommandFsmResult, StateChangeError,
    },
    variant::VariantValue,
};
use tokio::sync::mpsc;

use log::error;
use simplelog::*;

pub struct CustomStateMachine {
    // Custom fields, e.g., connection state, internal logic
}

#[async_trait]
impl CommandFsmHandler for CustomStateMachine {
    async fn on_startup(&mut self, status: &mut MechFsmStatusTuple) -> Result<(), anyhow::Error> {
        return Ok(());
    }

    async fn on_exec_command(
        &mut self,
        sender: &mpsc::Sender<CommandFsmMessage>,
        _command: &MechFsmCommandTuple,
        status: &mut MechFsmStatusTuple,
    ) -> Result<CommandFsmResult, anyhow::Error> {
        // Get a sender and clone it.
        let tx = sender.clone();
        let mut status_clone = status.clone();

        tokio::spawn(async move {
            println!("Taking forever to execute command...");
            tokio::time::sleep(Duration::from_millis(7000)).await;

            println!("Sending message that CmdDone...");

            // Feel free to write some data to the status
            let mut v = MechFsmCommandArgTuple::new();
            let _ = v.push_value_u16(1);
            status_clone.data = v;

            if let Err(err) = tx
                .send(CommandFsmMessage::ChangeState {
                    state: MechFsmState::CmdDone,
                    status: status_clone,
                })
                .await
            {
                error!("Failed to send change state command: {}", err);
            }
        });

        // Perform the execution...
        Ok(CommandFsmResult::Running)
    }

    async fn on_state_change(&mut self, new_state: &MechFsmState) -> Result<(), StateChangeError> {
        // Custom logic when the state changes.
        println!("State changed to: {:?}", new_state);
        return Ok(());
    }

    async fn read_command(
        &mut self,
        _command: &mut MechFsmCommandTuple,
    ) -> Result<(), anyhow::Error> {
        println!("Read the command...");
        return Ok(());
    }

    // fn process(&mut self, command: &MechFsmCommandTuple, status : &mut mechutil::command::MechFsmStatusTuple ) -> Result<(), anyhow::Error> {
    //     println!("TICKY TICK TICK!");
    //     return Ok(());
    // }

    async fn write_status(
        &mut self,
        status: &mut mechutil::command::MechFsmStatusTuple,
    ) -> Result<(), anyhow::Error> {
        println!("Write the status: {:?}", status.state);

        return Ok(());
    }

    async fn write_heartbeat(&mut self, heartbeat_count: i64) -> Result<(), anyhow::Error> {
        println!("heartbeat: {}", heartbeat_count);
        return Ok(());
    }
}

// Example of using the custom state machine
#[tokio::main]
async fn main() {
    CombinedLogger::init(vec![TermLogger::new(
        LevelFilter::Debug,
        Config::default(),
        TerminalMode::Mixed,
        ColorChoice::Auto,
    )])
    .unwrap();

    let mut status_hash_test = MechFsmStatusTuple::new();
    status_hash_test.state = MechFsmState::Init;
    status_hash_test.update_crc32();
    println!("STATUS CRC IS CALCULATED AS: {}", status_hash_test.crc);

    let mut cmd_hash_test = MechFsmCommandTuple::new();
    cmd_hash_test.transaction_id = 2000;
    cmd_hash_test.control = MechFsmControl::Idle;
    cmd_hash_test.index = 7;
    cmd_hash_test.sub_index = 9;
    cmd_hash_test.update_crc32();
    println!("COMMAND CRC IS CALCULAED AS: {}", cmd_hash_test.crc);

    let mut cmd_test = MechFsmCommandTuple::new();
    cmd_test.transaction_id = 32865;
    cmd_test.control = MechFsmControl::Idle;
    cmd_test.crc = 733694397;

    let var: VariantValue = cmd_test.try_into().unwrap();

    println!("VAR: {:?}\nDATA {:?}", var, cmd_test.data);

    let cmd: MechFsmCommandTuple;

    match var.try_into() {
        Ok(res) => cmd = res,
        Err(err) => panic!("Converion from variant->MechFsmCommandTuple failed.{}", err),
    }

    println!("RES CMD: {:?}", cmd);

    assert_eq!(cmd.transaction_id, cmd_test.transaction_id);
    assert_eq!(cmd.control, cmd_test.control);
    assert_eq!(cmd.crc, cmd_test.crc);
    assert_eq!(cmd.data.num_rows, cmd_test.data.num_rows);

    //let mech_actor = MechActor::new(tokio::time::Duration::from_secs(1));
    let custom_state_machine: Box<dyn CommandFsmHandler + Send> = Box::new(CustomStateMachine {});
    //let (tx_shutdown, _) = tokio::sync::broadcast::channel::<bool>(10);

    //let mut state_machine_actor = CommandFsmActor::new(mech_actor, custom_state_machine, tx_shutdown);
    let handle = CommandFsmHandle::new(custom_state_machine, Duration::from_millis(500));

    // Start processing commands (standardized flow)
    //let command = MechFsmCommandTuple::new(); // Example command
    // state_machine_actor.handle_exec_command(command).await.unwrap();

    tokio::time::sleep(Duration::from_millis(100)).await;

    let idle_cmd = MechFsmCommandTuple::from_control_code(MechFsmControl::Idle);

    if let Err(err) = handle.command(&idle_cmd).await {
        error!("Failed to inject idle command: {}", err);
    }

    tokio::time::sleep(Duration::from_millis(100)).await;

    let exec_cmd = MechFsmCommandTuple::from_control_code(MechFsmControl::Exec);

    if let Err(err) = handle.command(&exec_cmd).await {
        error!("Failed to inject execute command: {}", err);
    }

    tokio::time::sleep(Duration::from_millis(200)).await;

    let exec_cmd = MechFsmCommandTuple::from_control_code(MechFsmControl::AckDone);

    if let Err(err) = handle.command(&exec_cmd).await {
        error!("Failed to inject AckDone command: {}", err);
    }

    tokio::time::sleep(Duration::from_millis(100)).await;
    let _ = handle.shutdown().await;
}
