
package uk.co.wingpath.modbus;

import java.util.*;
import uk.co.wingpath.util.*;

/**
* Instances of this class specify how values larger than 16 bits are handled.
* <p>Instances are immutable.
*/
public class BigValueFlags
    implements Cloneable
{
    private final boolean littleEndian;
    private final boolean wordRegisters;
    private final boolean wordCount;

    /**
    * Constructs a {@code BigValueFlags} instance with the specified field
    * values.
    * @param littleEndian whether little-endian or big-endian
    * @param wordRegisters whether registers can hold arbitrary-sized values or
    * only 16 bits
    * @param wordCount whether count in request is number of 16-bit words or
    * number of values
    */
    public BigValueFlags (boolean littleEndian, boolean wordRegisters,
        boolean wordCount)
    {
        this.littleEndian = littleEndian;
        this.wordRegisters = wordRegisters;
        this.wordCount = wordCount;
    }

    /**
    * Constructs a {@code BigValueFlags} instance with all fields set to
    * {@code false}.
    */
    public BigValueFlags ()
    {
        this (false, false, false);
    }

    /**
    * Gets the little-endian flag.
    * @return little-endian flag.
    */
    public boolean getLittleEndian ()
    {
        return littleEndian;
    }

    /**
    * Gets the big-registers flag.
    * @return big-registers flag.
    */
    public boolean getWordRegisters ()
    {
        return wordRegisters;
    }

    /**
    * Gets the word-count flag.
    * @return word-count flag.
    */
    public boolean getWordCount ()
    {
        return wordCount;
    }

    /**
    * Computes how much a register address needs to be incremented to
    * refer to the next register.
    * @param size size of register in bytes.
    * @return address increment.
    */
    public int addressIncrement (int size)
    {
        if (size > 2 && wordRegisters)
            return (size + 1) / 2;
        return 1;
    }

    /** Returns the address of the next possible register after the supplied
    * address.
    * @param address register address.
    * @param size register size in bytes.
    * @return address of next register.
    */
    public int nextAddress (int address, int size)
    {
        return address + addressIncrement (size);
    }

    /**
    * Checks the register definitions and the big-value flags
    * for consistency.
    * @param registers array of register definitions.
    * @throws ValueException if there is an inconsistency.
    */
    public void checkRegisters (ModbusRegister [] registers)
        throws ValueException
    {
        for (int i = 0 ; i < registers.length - 1 ; i++)
        {
            ModbusRegister reg = registers [i];
            int address = reg.getAddress ();
            int size = reg.getValueSize ();
            ModbusRegister nextReg = registers [i + 1];
            int nextAddr = nextReg.getAddress ();

            if (nextAddress (address, size) > nextAddr)
            {
                int fileNum = reg.getFileNum ();
                String fileStr = fileNum == 0 ? "" : "File " + fileNum + ": ";
                throw new ValueException (fileStr + "Registers " + address +
                    " and " + nextAddr + " overlap");
            }
        }
    }

    /**
    * Checks a proposed register size for consistency with the current
    * register definitions and big-value flags.
    * @param registers array of register definitions.
    * @param register register whose size is to be changed.
    * @param size proposed size of register.
    * @throws ValueException if there is an inconsistency.
    */
    public void checkRegisterSize (ModbusRegister [] registers,
            ModbusRegister register, int size)
        throws ValueException
    {
        // Check that register does not overlap following address

        int index = Arrays.binarySearch (registers, register);
        if (index < 0)
        {
            throw new IllegalArgumentException (
                "Register not in registers array");
        }
        if (index + 1 >= registers.length)
            return;
        ModbusRegister reg = registers [index + 1];
        if (nextAddress (register.getAddress (), size) > reg.getAddress ())
        {
            int fileNum = reg.getFileNum ();
            String fileStr = fileNum == 0 ? "" : "File " + fileNum + ": ";
            throw new ValueException (fileStr +
                "Registers " + register.getAddress () + " and " +
                reg.getAddress () + " overlap");
        }
    }

    /**
    * Checks a proposed block of register definitions for consistency with
    * the current register definitions and big-value flags.
    * @param registers array of register definitions.
    * @param startaddr model address of first proposed register.
    * @param size size of each proposed register.
    * @param count number of registers in the block.
    * @throws ValueException if there is an inconsistency.
    */
    public void checkNewRegisters (ModbusRegister [] registers,
            int startaddr, int size, int count, int maxAddr)
        throws ValueException
    {
        int inc = addressIncrement (size);
        int endaddr = startaddr + count * inc - 1;
        if (endaddr > maxAddr || endaddr < startaddr)
        {
            throw new ValueException (
                "Address must be in range 0 to " + maxAddr);
        }

        for (ModbusRegister reg : registers)
        {
            int addr = reg.getAddress ();

            // Check that address not already defined within new register range

            if (addr >= startaddr && addr <= endaddr)
            {
                int fileNum = reg.getFileNum ();
                String fileStr = fileNum == 0 ? "" : "File " + fileNum + ": ";
                if ((addr - startaddr) % inc == 0)
                {
                    throw new ValueException (fileStr +
                        "Address " + addr + " is already used");
                }
                else
                {
                    throw new ValueException (fileStr +
                        "Registers " +
                        (addr - (addr - startaddr) % inc) +
                        " and " + addr + " overlap");
                }
            }

            // Check that start address does not overlap preceding register

            if (addr < startaddr &&
                nextAddress (addr, reg.getValueSize ()) > startaddr)
            {
                int fileNum = reg.getFileNum ();
                String fileStr = fileNum == 0 ? "" : "File " + fileNum + ": ";
                throw new ValueException ("M012", fileStr +
                    "Registers " + addr + " and " + startaddr +
                        " overlap");
            }
        }
    }

    @Override
    public boolean equals (Object obj)
    {
        if (!(obj instanceof BigValueFlags))
            return false;
        BigValueFlags bvf = (BigValueFlags) obj;
        return littleEndian == bvf.littleEndian &&
            wordRegisters == bvf.wordRegisters &&
            wordCount == bvf.wordCount;
    }

    @Override
    public int hashCode ()
    {
        return (littleEndian ? 4 : 0) +
            (wordRegisters ? 2 : 0) +
            (wordCount ? 1 : 0);
    }

    @Override
    public String toString ()
    {
        return "[BigValueFlags " +
            littleEndian + " " +
            wordRegisters + " " +
            wordCount + "]";
    }
}


