
package uk.co.wingpath.modsnmp;

import java.io.*;
import java.util.*;
import org.snmp4j.smi.OID;
import uk.co.wingpath.snmp.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.modbusgui.*;
import uk.co.wingpath.gui.*;
import uk.co.wingpath.xml.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.registration.*;

public class Settings
    implements Xml.Savable
{
    private static final OID oidPrefix = new OID ("1.3.6.1.4.1.34171.3");
    private final ModbusSettings modbus;
    private final ModbusServerSettings modbusServer;
    private final SnmpSettings snmp;
    private final DeviceTypes deviceTypes;
    private final Devices devices;
    private final ConstantOids constantOids;
    private final BooleanSettings columnVisibleSettings;
    private final Mib mib;
    private final LogSettings log;
    private final Variable<Boolean> useBrowser;
    private final boolean debugging;

    public Settings (Product product, boolean debugging)
    {
        this.debugging = debugging;
        modbus = new ModbusSettings (product);
        modbusServer = new ModbusServerSettings ();
        snmp = new SnmpSettings ();
        deviceTypes = new DeviceTypes ();
        devices = new Devices ();
        constantOids = new ConstantOids ();
        columnVisibleSettings = new BooleanSettings ();
        int logId = Registration.getLogId (product.getCode (),
            product.getMajorVersion ());
        mib = new Mib ("MODSNMP-" + logId);
        log = new LogSettings (debugging);
        useBrowser = new SimpleVariable<Boolean> (false);
    }

    public OID getOidPrefix ()
    {
        return new OID (oidPrefix).append (mib.getModuleNumber ());
    }

    public ModbusSettings getModbus ()
    {
        return modbus;
    }

    public ModbusServerSettings getModbusServer ()
    {
        return modbusServer;
    }

    public SnmpSettings getSnmp ()
    {
        return snmp;
    }

    public DeviceTypes getDeviceTypes ()
    {
        return deviceTypes;
    }

    public Devices getDevices ()
    {
        return devices;
    }

    public ConstantOids getConstantOids ()
    {
        return constantOids;
    }

    public BooleanSettings getColumnVisible ()
    {
        return columnVisibleSettings;
    }

    public Mib getMib ()
    {
        return mib;
    }

    public LogSettings getLog ()
    {
        return log;
    }

    public Variable<Boolean> getUseBrowser ()
    {
        return useBrowser;
    }

    public void save (Xml.Saver saver)
        throws IOException
    {
        saver.saveValue ("modbus", modbus);
        saver.saveValue ("snmp", snmp);
        saver.saveValue ("modbusServer", modbusServer);
        saver.saveValue ("columns", columnVisibleSettings);
        saver.saveValue ("deviceTypes", deviceTypes);
        saver.saveValue ("devices", devices);
        saver.saveValue ("constantOids", constantOids);
        saver.saveValue ("mib", mib);
        saver.saveValue ("logging", log);
        saver.saveValue ("usebrowser", useBrowser.getValue ());
    }

    public Xml.Loader getXmlLoader (Reporter reporter)
    {
        if (reporter == null)
            throw new NullPointerException ("reporter must not be null");
        return new XmlLoader (reporter);
    }

    private class XmlLoader
        extends Xml.AbstractLoader
    {
        private final Reporter reporter;
        private final Map<String,DeviceType> devMap;

        XmlLoader (Reporter reporter)
        {
            this.reporter = reporter;
            devMap = new TreeMap<String,DeviceType> ();
        }

        public Xml.Loader startChild (String tag)
        {
            if (tag.equalsIgnoreCase ("modbus"))
                return modbus.getXmlLoader (reporter);
            if (tag.equalsIgnoreCase ("modbusServer"))
                return modbusServer.getXmlLoader ();
            if (tag.equalsIgnoreCase ("snmp"))
                return snmp.getXmlLoader ();
            if (tag.equalsIgnoreCase ("columns"))
                return columnVisibleSettings.getXmlLoader ();
            if (tag.equalsIgnoreCase ("deviceTypes"))
                return deviceTypes.getXmlLoader ();
            if (tag.equalsIgnoreCase ("devices"))
                return devices.getXmlLoader (deviceTypes, modbus);
            if (tag.equalsIgnoreCase ("constantOids"))
                return constantOids.getXmlLoader ();
            if (tag.equalsIgnoreCase ("mib"))
                return mib.getXmlLoader ();
            if (tag.equalsIgnoreCase ("logging"))
                return log.getXmlLoader ();
            if (tag.equalsIgnoreCase ("usebrowser"))
            {
                return new Xml.BooleanLoader (new Xml.Receiver<Boolean> ()
                {
                    public void receive (Boolean value)
                    {
                        useBrowser.setValue (value);
                    }
                });
            }

            // Support for V1 configuration files.
            if (tag.equalsIgnoreCase ("oidmap"))
            {
                return new OidMapLoader (devMap);
            }

            return null;
        }
    }

    private class OidMapLoader
        extends Xml.AbstractLoader
    {
        private final Map<String,DeviceType> devMap;

        OidMapLoader (Map<String,DeviceType> devMap)
        {
            this.devMap = devMap;
            deviceTypes.deleteAll ();
            devices.deleteAll ();
        }

        public Xml.Loader startChild (String tag)
        {
            if (tag.equalsIgnoreCase ("entry"))
                return new OidEntryLoader (devMap);

            return null;
        }

        public void end (String val)
        {
            ArrayList<DeviceType> dts =
                new ArrayList<DeviceType> (devMap.values ());

            // Try to create device descriptions from common parts of
            // object descriptions.
            for (DeviceType dt : dts)
            {
                String description = dt.commonObjTypeDescription ();
                if (description != null)
                {
                    for (int i = 0 ; i < devices.getSize () ; ++i)
                    {
                        Device d = devices.getElementAt (i);
                        if (d.getDeviceType () == dt)
                            d.setDescription (description);
                    }
                }
            }

            // Try to combine equivalent device types.

            for (int i = 0 ; i < dts.size () ; ++i)
            {
                DeviceType dt1 = dts.get (i);

                for (int j = i + 1 ; j < dts.size () ; ++j)
                {
                    DeviceType dt2 = dts.get (j);
                    if (dt1.equiv (dt2))
                    {
                        for (int k = 0 ; k < devices.getSize () ; ++k)
                        {
                            Device d = devices.getElementAt (k);
                            if (d.getDeviceType () == dt2)
                            {
                                d.setDeviceType (dt1);
                            }
                        }
                        deviceTypes.deleteEntry (dt2);
                    }
                }
            }

            // Inform all list listeners of additions.

            for (DeviceType dt : dts)
                dt.valuesUpdated ();
            deviceTypes.valuesUpdated ();
            devices.valuesUpdated ();
        }
    }

    private class OidEntryLoader
        extends Xml.AbstractLoader
    {
        private final Map<String,DeviceType> devMap;
        private int number;
        private String description;
        private SnmpType snmpType;
        private ModbusType modbusType;
        private int modbusSize;
        private int address;
        private double offset;
        private double scale;
        private boolean writable;
        private ModbusInterface modbusInterface;
        private int slaveId;

        OidEntryLoader (Map<String,DeviceType> devMap)
        {
            this.devMap = devMap;
            number = 1;
            description = "";
            snmpType = SnmpType.Integer32;
            modbusType = ModbusType.Holding;
            modbusSize = 16;
            address = 0;
            offset = 0.0;
            scale = 1.0;
            writable = true;
            modbusInterface = null;
            slaveId = 1;
        }

        public Xml.Loader startChild (String tag)
        {
            if (tag.equalsIgnoreCase ("number"))
            {
                return new Xml.IntegerLoader (1, 65536,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            number = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("oid"))
            {
                return new Xml.StringLoader (
                    new Xml.Receiver<String> ()
                    {
                        public void receive (String value)
                        {
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("first"))
            {
                return new Xml.StringLoader (
                    new Xml.Receiver<String> ()
                    {
                        public void receive (String value)
                        {
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("description"))
            {
                return new Xml.StringLoader (
                    new Xml.Receiver<String> ()
                    {
                        public void receive (String value)
                        {
                            description = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("snmpType"))
            {
                return new Xml.EnumLoader<SnmpType> (SnmpType.class,
                    new Xml.Receiver<SnmpType> ()
                    {
                        public void receive (SnmpType value)
                        {
                            snmpType = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("modbusType"))
            {
                return new Xml.EnumLoader<ModbusType> (ModbusType.class,
                    new Xml.Receiver<ModbusType> ()
                    {
                        public void receive (ModbusType value)
                            throws ValueException
                        {
                            modbusType = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("modbusSize"))
            {
                return new Xml.IntegerLoader (1, 2000,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                            throws ValueException
                        {
                            modbusSize = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("address"))
            {
                return new Xml.IntegerLoader (0, 65535,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            address = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("writable"))
            {
                return new Xml.BooleanLoader (
                    new Xml.Receiver<Boolean> ()
                    {
                        public void receive (Boolean value)
                        {
                            writable = value;
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("offset"))
            {
                return new Xml.DoubleLoader (new Xml.Receiver<Double> ()
                {
                    public void receive (Double value)
                        throws ValueException
                    {
                        offset = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("scale"))
            {
                return new Xml.DoubleLoader (new Xml.Receiver<Double> ()
                {
                    public void receive (Double value)
                        throws ValueException
                    {
                        scale = value;
                    }
                });
            }

            if (tag.equalsIgnoreCase ("modbusInterface"))
            {
                return new Xml.StringLoader (
                    new Xml.Receiver<String> ()
                    {
                        public void receive (String value)
                            throws ValueException
                        {
                            modbusInterface =
                                modbus.getInterface (value);
                            if (modbusInterface == null)
                            {
                                throw new ValueException (
                                    value + ": No such interface");
                            }
                        }
                    });
            }

            if (tag.equalsIgnoreCase ("slaveId"))
            {
                return new Xml.IntegerLoader (1, 255,
                    new Xml.Receiver<Integer> ()
                    {
                        public void receive (Integer value)
                        {
                            slaveId = value;
                        }
                    });
            }

            return null;
        }

        public void end (String val)
            throws ValueException
        {
            if (!val.equals (""))
                throw new ValueException ("Value not allowed");
            if (modbusInterface == null)
                modbusInterface = modbus.getInterface ("default");
            if (modbusInterface == null)
                throw new ValueException ("Modbus interface missing");

            String di = "" + modbusInterface + slaveId;
            DeviceType deviceType = devMap.get (di);
            if (deviceType == null)
            {
                deviceType = new DeviceType ("D" + (devMap.size () + 1));
                deviceType.getBigValue ().setValue (
                    modbusInterface.getBigValue ().getValue ());
                deviceTypes.addEntry (deviceType);
                devMap.put (di, deviceType);
            }
            String devName = deviceType.getName ();

            Device device = devices.getDevice (devName);
            if (device == null)
            {
                device = new Device (devName);
                device.setDeviceType (deviceType);
                device.setModbusInterface (modbusInterface);
                device.setSlaveId (slaveId);
                devices.addDevice (device);
            }
            else
            {
                assert device.getDeviceType () == deviceType;
                assert device.getModbusInterface () == modbusInterface;
                assert device.getSlaveId () == slaveId;
            }

            if (modbusType == ModbusType.Holding ||
                modbusType == ModbusType.Input)
            {
                if (modbusSize != 16 && modbusSize != 32 && modbusSize != 64)
                {
                    throw new ValueException ("Invalid Modbus size: " +
                        modbusSize);
                }
            }
            if (modbusType == ModbusType.Discrete ||
                modbusType == ModbusType.Input)
            {
                writable = false;
            }
            String name = "O" + (deviceType.getSize () + 2);
            ObjType objType = new ObjType (name);
            objType.setNumber (number);
            objType.setDescription (description);
            objType.setSnmpType (snmpType);
            objType.setModbusType (modbusType);
            objType.setModbusSize (modbusSize);
            objType.setAddress (address);
            objType.setOffset (offset);
            objType.setScale (scale);
            objType.setWritable (writable);

            deviceType.addEntry (objType);
        }
    }

}

