var dataSouceDisplayNames = {
    CANbus: "CANbus",
    NMEA: "NMEA0183",
    Fastnet: "Fastnet",
    NoSource: "No Source"
}

class DataSource {
    constructor(displayName, internalName) {

        this.displayName = displayName;
        this.internalName = internalName;
        this.inputs = new Array();
        this.sourceData = new Object();
    }

    init() {}

    makeActive() {}

    makeInactive() {}

    setSourceData(internalName, key, value) {
        if (internalName == this.internalName) {
            this.sourceData[key] = value;
        }
    }
}

class FastnetDataSource extends DataSource {
    constructor() {
        super(dataSouceDisplayNames.Fastnet, dataSouceDisplayNames.Fastnet.toLowerCase());
    }
}

class NMEADataSource extends DataSource {
    constructor() {
        super(dataSouceDisplayNames.NMEA, dataSouceDisplayNames.NMEA.toLowerCase());

        this.ports = new Array();
    }

    init() {
        var deferred = new $.Deferred();
        var nmeaSection = "NMEA";
        var self = this;

        readSingle(nmeaSection, "Channels").done(function(channelList) {
            var async = new Array();
            var channels = channelList.split(';');

            channels.forEach(function(channel) {
                var self = this;
                async.push(readSingle(nmeaSection + channel, "Name").done(function(portName) {
                    self.ports.push(new DataSource(portName, channel));
                }));
            }, self);

            $.when.apply($, async).then(function() {
                self.ports.sort(function(a, b) {
                    return (a.internalName > b.internalName ? 1 : -1);
                });

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

        return deferred.promise();
    }
}

class CANbusDataSource extends DataSource {
    constructor(uniqueId) {
        super(dataSouceDisplayNames.CANbus, dataSouceDisplayNames.CANbus.toLowerCase());

        this.uniqueId = uniqueId;
        this.isValidDevice = false;
    }

    get address() {
        return this.sourceData['address'] || 0;
    }

    get serialNumber() {
        return this.sourceData['serialnumber'] || '';
    }

    get name() {
        return this.sourceData['model'] || '';
    }

    init(receiveType) {
        var async = new Array();
        var canSourceSection = "CanSource";
        var deferred = new $.Deferred();
        var self = this;

        async.push(
            readSingle(canSourceSection, receiveType).done(function(sourceList) {
                var validDevices = sourceList.split(';').map(source => source.toLowerCase());
                self.isValidDevice = validDevices.includes(self.uniqueId);
            })
        );
        async.push(
            readSection("CanDevices:" + self.uniqueId).done(function(section) {
                self.sourceData = $.extend(true, {}, self.sourceData, JSON.parse(section));
            })
        );

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

        return deferred.promise();
    }

    makeActive() {        
        var canState = null;
        var isInitial = true;
        var self = this;
		this.activeSource.intervalID = setInterval(function() {
			$.ajax({
				url: '\helpers.php',
				type: 'POST',
				data: {
					action: 'queryProcessor',
					query: 'FieldId=0x2801',
				},
				success: function(result) {
					var fieldMatchRegex = /(0x.*)=\s?(.*)\r/g;
					var match = fieldMatchRegex.exec(result);
                    
                    if (!!match) {
                        if ( parseInt(match[2]) != canState && !isInitial) {
                            // Reload from database
                            readSingle("CanSource", self.sensorType).done( function(canSource) {
                                if ( self.activeSource.deviceName == "" || 
                                     $.inArray( self.activeSource.deviceName, canSource.split(';') ) == -1 ) {
                                    self.activeSource.isValidDevice = false;
                                } else {
                                    self.activeSource.isValidDevice = true;
                                }
                                self.notifyAll();
                            });
                        }
                        canState = parseInt(match[2]);
                    }
				}
			});
		}, 2000);
    }

    makeInactive() {
        clearInterval(this.intervalID)
    }
}

class InterfaceBoard extends DataSource {
    constructor(displayName, internalName) {
        super(displayName, internalName);
    }

    get rawTempField() {
        return '0x0210';
    }

    get calcTempField() {
        return '0x1702';
    }

    get minDepth() {
        return this.sourceData["mindepth"];
    }

    set minDepth(depth) {
        this.sourceData["mindepth"] = depth;
    } 

    init() {
        var defaults = new Object();
        var deferred = new $.Deferred();
        var self = this;

        if (this.internalName.includes("speed depth")) {
            defaults = {
                "mindepth": 1,
                "tempoffset": 0
            }
        }
        self.sourceData = $.extend(true, {}, self.sourceData, defaults);

        readSection(this.internalName).done(function(section) {
            self.sourceData = $.extend(true, {}, self.sourceData, JSON.parse(section));
            deferred.resolve();
        });

        return deferred.promise();
    }
}

class AnalogueInterfaceChannel extends InterfaceBoard {
    constructor(displayName, internalName, channelNumber) {
        super(displayName, internalName);

        this.channelNumber = channelNumber;
    }

    get rawTempField() {
        return '0x180' + this.channelNumber;
    }

    get calcTempField() {
        return '0x' + (parseInt('0x1825', 16) + parseInt(this.internalName.slice(-1))).toString(16);
    }

    get mastRotationField() {
        return '0x180' + this.channelNumber;
    }
}

class AnalogueInterfaceBoard extends InterfaceBoard {
    constructor(displayName, internalName) {
        super(displayName, internalName);
    }

    init() {
        var async = new Array();
        var deferred = new $.Deferred();
        var numInputs = 0;
        var self = this;

        if (this.internalName.includes("main interface")) {
            numInputs = 4;
        } else if (this.internalName.includes("analogue")) {
            numInputs = 5;
        }

        for (var i = 1; i <= numInputs; i++) {
            (function(channelNumber) {
                var channel = self.internalName + ":ch" + channelNumber;
                async.push(readSection(channel).done(function(channelSection) {
                    var channelData = JSON.parse(channelSection);
                    var channelObject = new AnalogueInterfaceChannel(channelData['name'], channel, channelNumber);
                    $.extend(channelObject.sourceData, channelData);
                    self.inputs.push(channelObject);
                }));
            })(i);
        }

        async.push(readSection(self.internalName).done(function(boardSection) {
            $.extend(self.sourceData, JSON.parse(boardSection));
        }));

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

        return deferred.promise();
    }

    setSourceData(internalName, key, value) {
        for (var channel = 0; channel < this.inputs.length; channel++) {
            if (this.inputs[channel].internalName == internalName) {
                this.inputs[channel].sourceData[key] = value;
                break;
            }
        }
    }
}

getCanDevices = function(sensorType) {
    var deferred = new $.Deferred();
    var canSources = new Array();

    readSingle("CanSource", sensorType).done(function(sourceList) {
        var async = new Array();
        sourceList.split(';').forEach(function(canSource) {
            var canDataSource = new CANbusDataSource(canSource);
            async.push(canDataSource.init(sensorType));
            canSources.push(canDataSource);
        });

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

    return deferred.promise();
}

getBoards = function(sensorType) {
    var async = new Array();
    var boards = new Array();
    var deferred = new $.Deferred();
    var sensorBoardTypes = new Array();

    switch(sensorType) {
        case "Analogue":
        case "Heel":
        case "Rudder":
            sensorBoardTypes = ["Analogue Board", "Main Interface Board"];
            break;
        case "Environment":
            sensorBoardTypes = ["Speed Board", "Speed Depth Board", "Analogue Board", "Main Interface Board"]; 
            break;
        case "Speed":
            sensorBoardTypes = ["Speed Board", "Speed Depth Board"];
            break;
        case "Wind":
            sensorBoardTypes = ["Wind Board"];
            break;
        case "Depth":
            sensorBoardTypes = ["Depth Board", "Speed Depth Board"];
            break;
        default:
            boards = [];
            return deferred.resolve(boards).promise();
    }

    readSection('Boards').done(function(section) {
        var allBoards = JSON.parse(section);
        var validBoards = new Array();

        getServerMillis().done(function(milliseconds) {
            validBoards = Object.keys(allBoards).filter(function(key) {
                //Board must be valid for this sensor and have been active in the last 10 minutes
                return ($.inArray( key.split(':')[0], sensorBoardTypes.map(function(x) {
                    return x.toLowerCase();
                })) > -1) && (milliseconds - allBoards[key] < 600000);
            });

            $.each(validBoards, function(index, boardName) {
                if (boardName.includes("analogue") || boardName.includes("main interface")) {
                    var boardObject = new AnalogueInterfaceBoard(toTitleCase(boardName), boardName.toLowerCase());
                    async.push(boardObject.init().done(function() {
                        boards.push(boardObject);
                    }));
                } else {
                    var boardObject = new InterfaceBoard(toTitleCase(boardName), boardName.toLowerCase());
                    async.push(boardObject.init().done(function() {
                        boards.push(boardObject);
                    }));
                }
            });

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

    return deferred.promise();
}

getSources = function(sensorType)
{
    var availableInterfaces = new Array();
    var canbusDevices = [];
    var async = new Array();
    var deferred = new $.Deferred();

    switch(sensorType) 
    {
        case "Speed":
        {
            let nmeaSource = new NMEADataSource();
            async.push(nmeaSource.init());

            availableInterfaces.push(new DataSource(dataSouceDisplayNames.NoSource, dataSouceDisplayNames.NoSource.toLowerCase()));
            availableInterfaces.push(new FastnetDataSource()); 
            availableInterfaces.push(nmeaSource);
            availableInterfaces.push(new CANbusDataSource(""));
            break;
        }

        case "Depth":
        case "Wind":
        {
            let nmeaSource = new NMEADataSource();

            async.push(nmeaSource.init());

            availableInterfaces.push(new DataSource(dataSouceDisplayNames.NoSource, dataSouceDisplayNames.NoSource.toLowerCase()));
            availableInterfaces.push(nmeaSource);
            availableInterfaces.push(new CANbusDataSource(""));
            break;
        }

        case "Position":
        case "Heading":
        case "Rudder":
            availableInterfaces.push(new FastnetDataSource()); 
        case "Heel":
            var nmeaSource = new NMEADataSource();
            
            async.push(nmeaSource.init());
            availableInterfaces.push(nmeaSource);

            availableInterfaces.push(new CANbusDataSource(""));
            break;
        case "Analogue":
        case "Environment":
            break;
        default:
            return deferred.resolve(availableInterfaces, canbusDevices).promise();
    }

    async.push(getBoards(sensorType).done(function(boards) {
        availableInterfaces = $.merge(availableInterfaces, boards);
    }));

    // "Analogue" sensorType does not require canbus devices reading from the database
    if (sensorType != "Analogue") {
        let def = new $.Deferred();
        async.push(def.promise());

        let databaseKey = (sensorType == "Heel") ? "Sensor" : sensorType;
        readSingle("CanSource", databaseKey).done(function(canSourceSensorSection) {
            let canDevicesIds = canSourceSensorSection.split(";").filter(Boolean); // Filter removes empty strings ("")

            let deviceAsync = [];
            canDevicesIds.forEach(function(id) {
                deviceAsync.push(readSection("CanDevices:" + id).done(function(response) {
                    let deviceInfo = JSON.parse(response);
                    canbusDevices.push({ 
                        "uniqueid" : id,
                        "model" : (deviceInfo.model || ""),
                        "address" : (deviceInfo.address || 0),
                        "serialNumber" : (deviceInfo.serialnumber || ""),
                        "displayName": (deviceInfo.model || "") + " [" + (deviceInfo.serialnumber || "") + "]",
                        "isConnected" : true
                    });
                }))
            });

            $.when.apply($, deviceAsync).then(function() {
                def.resolve();
            });
        });
    }

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

    return deferred.promise();
}
