/**
 * Defines interface boards for webpages to make use of.
 * 
 * @category Webserver
 * @package ATProcessor
 * @author Sam Meredith <sam@aandtinstruments.com>
 * @version 0.1
 * 
 */

// Require string utils
let isLoaded = $.getScript('/js/utils.string.js');

/**
 * Enum for analogue input types
 */
const eAnalogueType = Object.freeze({
    "Input Name": "",
    "User Defined": 0,
    "Rudder": 1,
    "Mast Rotation": 2,
    "Heel Angle": 3,
    "Trim": 4,
    "AirTemp": 5,
    "SeaTemp": 6,
    "Man-Overboard": 7,
    "Keel Angle": 8,
    "Canard Angle": 9,
    "Daggerboard Position": 10,
    "Boom Position": 11
});

/**
 * Enum for interface board types
 */
const eInterfaceType = Object.freeze({
    "Default": 0,
    "Speed": 1,
    "SpeedDepth": 2,
    "MainInterface": 3,
    "Analogue": 4,
    "Wind": 5,
    "Depth": 6,
    "DLA2": 7,
    "EnumLength": 8
});

/**
 * Default analogue channel config
 */
const defaultAnalogueConfig = {
    "maximumvalue"      : 5.0,
    "maximumvoltage"    : 5.0,
    "minimumvalue"      : 0.0,
    "minimumvoltage"    : 0.0,
    "name"              : "Input Name",
    "referencevoltage"  : "5V",
    "tempoffset"        : "0",
    "tempcoeff"         : 3977
}

/**
 * Factory class for generating interface boards
 */
class InterfaceFactory {
    constructor() {}

    // Create a concrete interface from a given interface name
    getInterface(interfaceName) {
        let deferred = new $.Deferred();
        let m_interface = null;

        isLoaded.done(function() {
            if (interfaceName.includes("speed depth")) {
                m_interface = new SpeedDepthBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("speed")) {
                m_interface = new SpeedBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("depth")) {
                m_interface = new DepthBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("analogue")) {
              m_interface = new AnalogueBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("main interface")) {
                m_interface = new MainInterfaceBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("wind")) {
                m_interface = new WindBoard(toTitleCase(interfaceName), interfaceName);
            } else if (interfaceName.includes("dla2")) {
                m_interface = new DLA2Board(toTitleCase(interfaceName), interfaceName);
            } else {
                m_interface = new InterfaceBoard(toTitleCase(interfaceName), interfaceName);
            }

            m_interface.init().done(function() { deferred.resolve(m_interface); });
        });

        return deferred.promise();
    }
}

/**
 * Base interface board class
 * All boards have a display name for use on the webserver and an internal name for db storage
 */
class InterfaceBoard {
    constructor(displayName, internalName) {
        this.displayName = displayName;
        this.internalName = internalName;

        this.data = {
            "tempoffset": "0"
        };
        this.interfaceType = eInterfaceType.Default;
    }

    init() {
        return $.Deferred().resolve();
    }

    persistChannelData(category) {
        return writeToDatabase([{ 'section': this.internalName, 'key': "name", 'value': this.displayName }], category);
    }

    // Generic function to set a single property in inheriting classes
    setProperty(internalName, key, value) {
        let setProperty = false;

        if (this.internalName == internalName) {
            this.data[key] = value;
            setProperty = true;
        }

        return setProperty;
    }
    // Generic function to set multiple properties in inheriting classes
    setData(internalName, object) {
        let setProperty = false;

        if (this.internalName == internalName) {
            this.data = $.extend({}, this.data, object);
            setProperty = true;
        }

        return setProperty;
    }
    // Set interface name
    setName(internalName, name) {
        if (this.internalName == internalName) {
            this.displayName = name;
        }
    }
}

/**
 * Analogue boards have 5 analogue inputs and a 20mA input
 */
class AnalogueBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);
        
        this.analogueInputs = [];
        this.interfaceType = eInterfaceType.Analogue;
    }

    init() {
        let async = [];
        let deferred = new $.Deferred();
        let numAnalogueInputs = 5;
        let self = this;

        for (let i = 1; i <= numAnalogueInputs; i++) {
            let channel = self.internalName + ":ch" + i;
            async.push(readSection(channel).done(function(sectionJson) {
                var section = JSON.parse(sectionJson);

                self.analogueInputs.push(
                    {
                        analogueType: section.function,
                        channel: i,
                        data: $.extend({}, defaultAnalogueConfig, section),
                        displayName: section.name,
                        internalName: channel
                    }
                )
            }));
        }

        async.push(readSingle(self.internalName, "Name").done(function(displayName) {
            self.displayName = displayName || "Board Name";
        }));

        $.when.apply($, async).then(function() {
            self.analogueInputs.sort(function(a, b) {
                return parseInt(a.channel) - parseInt(b.channel);
            });

            deferred.resolve();
        });

        return deferred.promise();
    }

    persistChannelData(category) {
        var databaseArray = new Array();

        databaseArray.push({ 'section': this.internalName, 'key': "name", 'value': this.displayName });

        $.each(this.analogueInputs, function(idx, val) {
            $.each(val.data, function(key, value) {
                databaseArray.push(
                    { 'section': val.internalName, 'key': key, 'value': value }
                );
            })
        });

        return writeToDatabase(databaseArray, category);
    }

    // Extend setProperty to also check analogue inputs
    setProperty(internalName, key, value) {
        let setProperty = false;

        if (this.internalName == internalName) {
            this.data.key = value;
            setProperty = true;
        } else if (internalName.startsWith(this.internalName)) {
            // Then it is an analogue input of this interface
            let analogueInput = this.analogueInputs.find(x => x.internalName == internalName);
            analogueInput.data[key] = value;
            setProperty = true;
        }

        return setProperty;
    }

    setData(internalName, object) {
        let setProperty = false;

        if (this.internalName == internalName) {
            this.data = $.extend({}, this.data, object);
            setProperty = true;
        } else if (internalName.startsWith(this.internalName)) {
            // Then it is an analogue input of this interface
            let analogueInput = this.analogueInputs.find(x => x.internalName == internalName);
            analogueInput.data = $.extend({}, analogueInput.data, object);
            setProperty = true;
        }

        return setProperty;
    }
}

/**
 * Depth sounder - superceded by SpeedDepth
 */
class DepthBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);

        this.interfaceBoard = eInterfaceType.Depth;
    }

    init() {
        let self = this;

        return readSingle(this.internalName, "MinDepth").done(function(minDepth) {
            self.data.minDepth = minDepth || 0;
        });
    }
}

/**
 * DLA Boards have 1 analogue input
 */
class DLA2Board extends AnalogueBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);

        this.analogueInputs = [];
        this.interfaceType = eInterfaceType.DLA2;
    }

    init() {
        let async = [];
        let deferred = new $.Deferred();
        let numAnalogueInputs = 1;
        let self = this;

        for (let i = 1; i <= numAnalogueInputs; i++) {
            let channel = self.internalName + ":ch" + i;
            async.push(readSection(channel).done(function(sectionJson) {
                var section = JSON.parse(sectionJson);

                self.analogueInputs.push(
                    {
                        analogueType: section.function,
                        channel: i,
                        data: $.extend({}, defaultAnalogueConfig, section),
                        displayName: section.name,
                        internalName: channel
                    }
                )
            }));
        }

        async.push(readSingle(self.internalName, "Name").done(function(displayName) {
            self.displayName = displayName || "Board Name";
        }));

        $.when.apply($, async).then(function() {
            deferred.resolve();
        });

        return deferred.promise();
    }
}

/**
 * Main interface boards have 4 analogue inputs
 */
class MainInterfaceBoard extends AnalogueBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);
        
        this.analogueInputs = [];
        this.interfaceType = eInterfaceType.MainInterface;
    }

    init() 
    {
        let async = [];
        let deferred = new $.Deferred();
        let numAnalogueInputs = 4;
        let startAnalogue = 1;
        let self = this;

        async.push(readSingle(self.internalName, "Name").done(function(displayName) 
        {
            self.displayName = displayName || "Board Name";
        }));

        readSingle("Hardware", "atpversion").done(function(versionResult) 
        {
            let version = parseInt(versionResult || 1);

            if (version == 2) startAnalogue = 4;

            for (let i = startAnalogue; i <= numAnalogueInputs; i++) 
            {
                let channel = self.internalName + ":ch" + i;
                async.push(readSection(channel).done(function(sectionJson) 
                {
                    var section = JSON.parse(sectionJson);

                    self.analogueInputs.push(
                        {
                            analogueType: section.function,
                            channel: i,
                            data: $.extend({}, defaultAnalogueConfig, section),
                            displayName: section.name,
                            internalName: channel
                        }
                    )
                }));
            }

            $.when.apply($, async).then(function() 
            {
                self.analogueInputs.sort(function(a, b) 
                {
                    return parseInt(a.channel) - parseInt(b.channel);
                });

                deferred.resolve();
            });
        });        

        return deferred.promise();
    }
}

/**
 * Speed input - superceded by SpeedDepth
 */
class SpeedBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);
        
        this.interfaceType = eInterfaceType.Speed;
    }

    init() {
        let self = this;

        return readSingle(this.internalName, "tempoffset").done(function(offset) {
            self.data.tempoffset = offset || 0;
        });
    }
}

/**
 * SpeedDepth input has a sea temperature input
 */
class SpeedDepthBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);

        this.interfaceType = eInterfaceType.SpeedDepth;
    }

    init() {
        let self = this;

        return readSection(this.internalName).done(function(sectionJson) {
            var section = JSON.parse(sectionJson);

            self.data.tempoffset = section.tempoffset || 0;
            self.data.minDepth = section.mindepth || 0;
        });
    }
}

/**
 * Wind board
 */
class WindBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);

        this.interfaceType = eInterfaceType.Wind;
    }
}
