class IowPort:
    """
    Represents an IO-Warrior port consisting of 8 bits.
    """
    def __init__(self, index, io_mask=0xFF, initial_data=None):
        """
        :param index: Port number (0...N)
        :param io_mask: Direction mask (1=Input, 0=Output) or integer 0-255
        :param initial_data: Initial value for output pins. If None, matches io_mask (pull-ups for inputs).
        """
        self._index = index
        self._direction = io_mask & 0xFF 
        
        if initial_data is None:
            self._data = self._direction
        else:
            self._data = initial_data & 0xFF
            
        self._special = 0x00
        self._existence = 0xff
        self._listeners = []
        self._initialized = False

    @property
    def index(self):
        return self._index

    @property
    def direction(self):
        return self._direction

    @direction.setter
    def direction(self, value):
        self._direction = value & 0xFF

    @property
    def data(self):
        return self._data & 0xFF

    @data.setter
    def data(self, value):
        # Update data, respecting direction (only outputs can be changed via setData)
        # Java: data = value & invertedMask (where invertedMask is outputs)
        inverted_mask = (0x7FFFFFFF - self._direction) & 0xFF
        self._data = (value & 0xFF) & inverted_mask

    def set_bit(self, bit):
        if 0 <= bit <= 7:
            mask = 1 << bit
            if (self._direction & mask) != mask: # if no input
                if (self._data & mask) != mask: # if this bit is not set
                    self._data |= mask
        else:
            raise ValueError("Invalid bit number")

    def clear_bit(self, bit):
        if 0 <= bit <= 7:
            mask = 1 << bit
            if (self._direction & mask) != mask: # if no input
                if (self._data & mask) == mask: # if this bit is set
                    self._data &= ~mask
        else:
            raise ValueError("Invalid bit number")

    def is_bit_set(self, bit):
        if 0 <= bit <= 7:
            return (self._data & (1 << bit)) != 0
        return False

    def is_bit_clear(self, bit):
        if 0 <= bit <= 7:
            return not self.is_bit_set(bit)
        return False

    def get_data_to_write(self):
        """
        Returns the content prepared for write operation.
        Usually for IOW: To read an input, we must write a '1' to the port pin (Open Drain).
        So if direction is Input (1), we write 1.
        If direction is Output (0), we write data (0 or 1).
        
        Java: return data | direction;
        This implies: 
        If Dir bit is 1 (Input), result bit is 1 (High/Pullup).
        If Dir bit is 0 (Output), result bit is Data bit.
        """
        return (self._data | self._direction) & 0xFF

    def add_port_change_listener(self, listener):
        if listener not in self._listeners:
            self._listeners.append(listener)

    def remove_port_change_listener(self, listener):
        if listener in self._listeners:
            self._listeners.remove(listener)

    def _notify_listeners(self):
        for listener in self._listeners:
            # Equivalent to Java's (new NotifierThread(this, pcl)).start()
            import threading
            t = threading.Thread(target=self._notifier_run, args=(listener,), daemon=True)
            t.start()

    def _notifier_run(self, listener):
        try:
            # Listener can be a callable or an object with port_changed method
            if hasattr(listener, 'port_changed'):
                listener.port_changed(self)
            elif callable(listener):
                listener(self)
        except Exception as e:
            print(f"Error in PortChangeListener (Port {self._index}): {e}")

    def set_data_from_read(self, read_data):
        """
        Updates internal data from a read report.
        """
        read_data &= 0xFF
        
        # Java logic: only update data if direction != 0 (at least one input)
        # In python we should at least check if it's the first read (init)
        if not self._initialized:
            self._data = read_data
            self._initialized = True
        elif self._direction != 0:
            if self._data != read_data:
                self._data = read_data
                self._notify_listeners()

    def set_existence(self, mask):
        self._existence = mask & 0xFF

    def get_special(self):
        return self._special

    def set_special(self, mask):
        self._special = mask & 0xFF

    def __str__(self):
        """
        Returns the String representation of this port.
        'IoMask' describes the pin direction (MSB first):
        - I - Input
        - O - Output
        - S - Dedicated by special mode function
        - . - Not existent
        'Data' describes the current pin status (MSB first):
        - 0 - Low
        - 1 - High
        - X - Don't care (dedicated by special mode function).
        - . - Not existent.
        """
        mask_chars = []
        for i in range(7, -1, -1):
            bit_mask = 1 << i
            if (self._special & bit_mask) == bit_mask:
                mask_chars.append('S')
            elif (self._existence & bit_mask) != bit_mask:
                mask_chars.append('.')
            elif (self._direction & bit_mask) == bit_mask:
                mask_chars.append('I')
            else:
                mask_chars.append('O')
        
        data_chars = []
        for i in range(7, -1, -1):
            bit_mask = 1 << i
            if (self._special & bit_mask) == bit_mask:
                data_chars.append('X')
            elif (self._existence & bit_mask) != bit_mask:
                data_chars.append('.')
            elif (self._data & bit_mask) == bit_mask:
                data_chars.append('1')
            else:
                data_chars.append('0')
                
        return f"P{self._index}[IoMask[{''.join(mask_chars)}],Data[{''.join(data_chars)}],Listener[{len(self._listeners)}]]"

    def __repr__(self):
        return self.__str__()
