import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;

public class Node extends Thread {
    private int my_id, round, leader_id, msg, size;
    private volatile int inmsg;
    private String status;
    private boolean finished;
    private final ReentrantLock lock = new ReentrantLock();

    // We actually now the complete network topology =>
    private ArrayList<Node> network;

    // Constructor

    public Node(int id) {
        this.my_id = id;
        this.round = 0; // Start at round 0 so Node 1's time slice (0) can be hit
        this.leader_id = -1; // Will be set when node decides
        if (my_id == 1) {
            msg = my_id; // Node 1 starts with msg = my_id
        } else {
            msg = -1; // we will simulate the null with -1
        }
        status = "unknown"; // All nodes start with status = unknown
        this.inmsg = -1; // like nothing has arrived
        this.network = new ArrayList<>();
        this.size = 0; // Will be set when setNetwork is called
        this.finished = false;
    }

    // Sending function - msgs_i (message generation function)
    public void message() {
        // Always send msg value (even if -1/null) to next node
        if (my_id == size) { // last node sends to the first
            network.get(0).setInmsg(msg);
        } else {
            network.get(my_id).setInmsg(msg); // we send our message to the next node
        }
    }

    // Setter for inmsg with ReentrantLock
    public void setInmsg(int value) {
        lock.lock();
        try {
            this.inmsg = value; // Always accept new messages
        } finally {
            lock.unlock();
        }
    }

    // Trasmision function - trans_i (transition function)
    public void trasmision() {
        lock.lock();
        try {
            int m = inmsg; // Store received message
            inmsg = -1; // msg = null (clear the received message buffer)

            if (status.equals("unknown")) {
                // If the received message is non-null (a UID)
                if (m != -1) {
                    status = "non leader";
                    leader_id = m;
                    msg = m; // Forward the received message
                }
                // Else If (round = (my_id - 1) * n)
                else if (round == ((my_id - 1) * size)) {
                    status = "leader";
                    leader_id = my_id;
                    msg = my_id;
                }
            }
            // If we're already a leader and receive our own message, stay as leader
            else if (status.equals("leader") && m == my_id) {
                // Our message completed the ring - we're confirmed as leader
                // Do nothing, stay as leader
            }
            // If we're a leader but receive someone else's message, become non-leader
            else if (status.equals("leader") && m != -1 && m != my_id) {
                status = "non leader";
                leader_id = m;
                msg = m;
            }

            // Termination condition: allow enough rounds for message propagation
            if (!status.equals("unknown") && round >= size * size) {
                finished = true;
            }

            round = round + 1; // round_i = round + 1
        } finally {
            lock.unlock();
        }
    }

    // ToString

    public String toString() {
        return ("ID = " + my_id + " | status = " + status + " | leader_id = " + leader_id + " | round = " + round);
    }

    @Override
    public void run() {
        while (!finished) {
            try {
                trasmision(); // Check status first
                message(); // Then send message
                Thread.sleep(100); // Shorter sleep for better propagation
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // Send one final message after deciding status
        message();
        System.out.println("Node " + my_id + " finished: " + status);
    }

    // Public getter

    public int getIdH() {
        return this.my_id;
    }

    public int getSize() {
        return this.size;
    }

    public void setNetwork(ArrayList<Node> network) {
        this.network = network;
        this.size = network.size();
    }

}
