class SensorModel extends ObservableModel
{
    constructor(sensorNumber, sensorType)
    {
        super();

        this.availableSources = [];
        this.listOfCanDevices = [];

        this.sensorSection = sensorType + 'Source' + sensorNumber;
        this.sensorCalibrationData = {};
        this.sensorNumber = sensorNumber;
        this.sensorType = sensorType;

        this.widgets = {};
    }

    get sensorName() {
        return this.sensorCalibrationData[this.sensorSection]['name'];
    }
    set sensorName(sensorName) {
        this.sensorCalibrationData[this.sensorSection]['name'] = sensorName;
    }

    get sensorSource() {
        return this.sensorCalibrationData[this.sensorSection]['source'];
    }
    set sensorSource(sensorSource) {
        this.activeSource.makeInactive();

        this.sensorCalibrationData[this.sensorSection]['source'] = sensorSource;
        this.activeSource.makeActive.apply(this);

        this.setDefaultInstance();
        this.resetDrawableWidgets();
    }

    get uniqueid()
    {
        return this.sensorCalibrationData[this.sensorSection].uniqueid;
    }

    set uniqueid(uniqueid)
    {
        this.sensorCalibrationData[this.sensorSection].uniqueid = uniqueid;
    }

    get sensorInstance() {
        if (this.sensorSource == "canbus")
        {
            const canDevice = this.listOfCanDevices.find(device => {
                return device.uniqueid === this.uniqueid;
            });

            return canDevice === undefined ? "0" : canDevice.address;
        }
        else
            return this.sensorCalibrationData[this.sensorSection]['instance'];
    }
    set sensorInstance(sensorInstance) {
        this.sensorCalibrationData[this.sensorSection]['instance'] = sensorInstance;
        //Changing instance requires certain fields to redraw
        this.resetDrawableWidgets();
    }

    get activeSource() {
        return this.availableSources.find(source => (source.internalName == this.sensorSource.toLowerCase())) || new DataSource("", "");
    }

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

        var defaults = {
            'source': dataSouceDisplayNames.NoSource.toLowerCase(), 
            'instance': '0',
            'name': '',
            'uniqueid': '0'
        }
        self.sensorCalibrationData[self.sensorSection] = $.extend({}, self.sensorCalibrationData[self.sensorSection], defaults);

        async.push(
            getSources(this.sensorType).done(function(sources, canbusDevices) {
                self.availableSources = sources;
                self.listOfCanDevices = canbusDevices;
            })
        );
        async.push(
            this.getSensorData().done(function(sensorData) {
                $.extend(true, self.sensorCalibrationData, sensorData);
            })
        );

        $.when.apply($, async).then(function() {
            self.activeSource.makeActive.apply(self);

            // If uniqueid for source isn't stored in listOfCanDevices, it is probably not connected. Add it to the list.
            if (self.sensorCalibrationData[self.sensorSection].uniqueid !== "" && self.sensorCalibrationData[self.sensorSection].uniqueid !== "0") {
                if (self.listOfCanDevices.find(device => device.uniqueid === self.sensorCalibrationData[self.sensorSection].uniqueid) === undefined) {
                    self.listOfCanDevices.push({ 
                        "uniqueid" : self.sensorCalibrationData[self.sensorSection].uniqueid,
                        "model" : "",
                        "address" : "0",
                        "serialNumber" : "",
                        "displayName": "Unconnected Device [ID:" + self.sensorCalibrationData[self.sensorSection].uniqueid + "]",
                        "isConnected" : false
                    });
                }
            }     

            self.setDrawableWidgets();
            self.notifyAll();
            deferred.resolve();
        });

        return deferred.promise();
    }

    getSensorData() {
        var deferred = new $.Deferred();
        var self = this;
        var sensorData = new Object();

        var url = new URL(window.location.href);
        if (url.searchParams.get("params") != null) {
            sensorData = JSON.parse(url.searchParams.get("params"));
            deferred.resolve(sensorData);
        } else {
            readSection(this.sensorSection).done(function(section) {
                sensorData[self.sensorSection] = $.extend({}, JSON.parse(section));
                deferred.resolve(sensorData);
            });
        }

        return deferred.promise();
    }

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

        $.each(this.sensorCalibrationData, function(section, sectionKeys) {
            $.each(sectionKeys, function(key, value) {
                databaseArray.push(
                    {'section': section, 'key': key, 'value': value}
                );
            });
        });

        return writeToDatabase(databaseArray, category);
    }

    resetDrawableWidgets() {
        this.widgets = new Array();
        this.setDrawableWidgets();
    }

    setDefaultInstance() 
    {
        switch (this.sensorSource) {
            case dataSouceDisplayNames.CANbus.toLowerCase():
                this.sensorInstance = this.availableSources.find(source => source.internalName == dataSouceDisplayNames.CANbus.toLowerCase()).address;
                break;
            case dataSouceDisplayNames.NMEA.toLowerCase():
                this.sensorInstance = 1;
                break;

            case dataSouceDisplayNames.Fastnet.toLowerCase():
            case dataSouceDisplayNames.NoSource.toLowerCase():
            {
                this.sensorInstance = 0;
                break;
            }

            default:
                this.sensorInstance = this.sensorSource.slice(-1);
                break;
        }
    }
}

class SpeedSensorModel extends SensorModel
{
    constructor(sensorNumber)
    {
        super(sensorNumber, 'Speed');
    }

    get calibrationFactor() {
        return this.sensorCalibrationData[this.sensorSection]['calibrationfactor'];
    }
    set calibrationFactor(calibrationFactor) {
        this.sensorCalibrationData[this.sensorSection]['calibrationfactor'] = calibrationFactor;
    }

    get computationEstimator() {
        return this.sensorCalibrationData['SpeedComputationEstimator']['Time'];
    }
    set computationEstimator(estimator) {
        this.sensorCalibrationData['SpeedComputationEstimator']['Time'] = estimator;
    }

    get isPulseSource() {
        return this.sensorSource.toLowerCase().includes('board');
    }

    get pulseCalibration() {
        return this.sensorCalibrationData[this.sensorSection]['pulsefreq'];
    }
    set pulseCalibration(pulseCalibration) {
        this.sensorCalibrationData[this.sensorSection]['pulsefreq'] = pulseCalibration;
    }

    get useSoG() {
        return this.sensorCalibrationData['SpeedInput']['UseGPS'];
    }
    set useSoG(useSoG) {
        this.sensorCalibrationData['SpeedInput']['UseGPS'] = useSoG;
        this.resetDrawableWidgets();
    }

    getSensorData() {
        var async = new Array();
        var deferred = new $.Deferred();
        var self = this;
        var sensorData = new Object();

        var defaults = {
            'calibrationfactor': '1.0'
        }
        sensorData[self.sensorSection] = $.extend({}, defaults);

        async.push(
            super.getSensorData().done(function(data) {
                $.extend(true, sensorData, data);

                // Set sensorData[self.sensorSection].calibrationfactor to a default value,
                // if there is no value stored in the database.
                if (sensorData[self.sensorSection].calibrationfactor.length === 0)
                    sensorData[self.sensorSection].calibrationfactor = defaults.calibrationfactor;
            })
        );
        async.push(
            readSingle('SpeedInput', 'UseGPS').done(function(useGPS) {
                sensorData['SpeedInput'] = {'UseGPS' : (useGPS == true)};
            })
        );
        async.push(
            readSingle('SpeedComputationEstimator', 'Time').done(function(estimator) {
                sensorData['SpeedComputationEstimator'] = {'Time' : estimator};
            })
        );

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

        return deferred.promise();
    }

    init() {
        var defaults = {
            'calibrationfactor': '1',
            'pulsefreq': '0.5'
        }
        this.sensorCalibrationData[this.sensorSection] = $.extend({}, this.sensorCalibrationData[this.sensorSection], defaults);

        return super.init();
    }

    persistData() {
        return super.persistData("SpeedLog").done(function() {
            //If SoG is being used, MOB functionality must be forced to GPS only
            return writeSingle('MOB', 'Position', 'gps', '');
        });
    }

    setDrawableWidgets() {

        if (this.useSoG != true) 
        {
            switch (this.sensorSource.toLowerCase()) 
            {
                case dataSouceDisplayNames.CANbus.toLowerCase():
                {
                    this.widgets['displayCANbusDevice'] = true;
                    this.widgets['displayDataCalibration'] = true;
                    break;
                }

                case dataSouceDisplayNames.NMEA.toLowerCase():
                {
                    this.widgets['displayNMEAPortSelection'] = true;
                    this.widgets['displayDataCalibration'] = true;
                    break;
                }

                case dataSouceDisplayNames.Fastnet.toLowerCase():
                {
                    this.widgets['displayDataCalibration'] = true;
                    break;
                }

                case dataSouceDisplayNames.NoSource.toLowerCase():
                {
                    break;
                }

                default:
                {
                    this.widgets['displayPulseCalibration'] = true;
                    break;
                }
            }
        }

        this.widgets['displaySourceSelection'] = true;
        this.widgets['displaySoGToggle'] = true;
        this.widgets['displayButtonBox'] = true;
    }
}

class DepthSensorModel extends SensorModel {
    constructor(sensorNumber) {
        super(sensorNumber, "Depth");
    }

    get datum() {
        return this.sensorCalibrationData[this.sensorSection]['datum'];
    }
    set datum(datum) {
        this.sensorCalibrationData[this.sensorSection]['datum'] = datum;
    }

    init() {
        var defaults = {
            'datum': '0.0'
        }
        this.sensorCalibrationData[this.sensorSection] = $.extend({}, this.sensorCalibrationData[this.sensorSection], defaults);
        return super.init();
    }

    persistData() {
        let self = this;
        return super.persistData("Depth").then(function() {

            var databaseArray = new Array();

            if (self.sensorSource.toLowerCase().includes('board')) {
                // Save the minimum depth if board.
                databaseArray.push(
                    { 'section': self.sensorSource, 'key': 'minDepth', value: self.activeSource.minDepth }
                );
            }
            
            databaseArray.push(
                { 'section': self.sensorSection, 'key': 'datum', value: self.datum }
            );

            return writeToDatabase(databaseArray, 'Depth');
        });
    }

    setDrawableWidgets() {
        switch (this.sensorSource.toLowerCase()) {
            case dataSouceDisplayNames.CANbus.toLowerCase():
                this.widgets['displayCANbusDevice'] = true;
                break;
            case dataSouceDisplayNames.NMEA.toLowerCase():
                this.widgets['displayNMEAPortSelection'] = true;
                break;
            case dataSouceDisplayNames.NoSource.toLowerCase():
                break;
            default:
                this.widgets['displayMinDepth'] = true;
                break;
        }
        this.widgets['displaySourceSelection'] = true;
    }
}

class WindSensorModel extends SensorModel {
    constructor(sensorNumber) {
        super(sensorNumber, "Wind");

        this.supportsGyroData = false;
    }

    get mhuOffset() {
        return this.sensorCalibrationData[this.sensorSection]['angleoffset'];
    }
    set mhuOffset(mhuOffset) {
        this.sensorCalibrationData[this.sensorSection]['angleoffset'] = mhuOffset;
    }

    get calibrationFactor() {
        return this.sensorCalibrationData[this.sensorSection]['calibrationfactor'];
    }
    set calibrationFactor(calibrationFactor) {
        this.sensorCalibrationData[this.sensorSection]['calibrationfactor'] = calibrationFactor;
    }

    get computationEstimator() {
        return this.sensorCalibrationData['Wind Calibration']['Time'];
    }
    set computationEstimator(estimator) {
        this.sensorCalibrationData['Wind Calibration']['Time'] = estimator;
    }

    get heelAngleCorrection() {
        return (this.sensorCalibrationData[this.sensorSection]['heelanglecorrenabled'] == true);
    }
    set heelAngleCorrection(heelAngleCorrection) {
        this.sensorCalibrationData[this.sensorSection]['heelanglecorrenabled'] = (heelAngleCorrection == true);
    }

    get heelSpeedCorrection() {
        return (this.sensorCalibrationData[this.sensorSection]['heelspeedcorrenabled'] == true);
    }
    set heelSpeedCorrection(heelSpeedCorrection) {
        this.sensorCalibrationData[this.sensorSection]['heelspeedcorrenabled'] = (heelSpeedCorrection == true);
    }

    get mastHeight() {
        return this.sensorCalibrationData[this.sensorSection]['mastheight'];
    }
    set mastHeight(mastHeight) {
        this.sensorCalibrationData[this.sensorSection]['mastheight'] = mastHeight;
    }

    get mastMotionAWAEnabled()
    {
        return (this.sensorCalibrationData[this.sensorSection]['mastmotionawaenabled'] == true);
    }
    set mastMotionAWAEnabled(enabled)
    {
        this.sensorCalibrationData[this.sensorSection]['mastmotionawaenabled'] = (enabled == true);
    }

    get mastMotionAWSEnabled()
    {
        return (this.sensorCalibrationData[this.sensorSection]['mastmotionawsenabled'] == true);
    }
    set mastMotionAWSEnabled(enabled)
    {
        this.sensorCalibrationData[this.sensorSection]['mastmotionawsenabled'] = (enabled == true);
    }

    get mastRotation() {
        return (this.sensorCalibrationData[this.sensorSection]['mastrotationenabled'] == true);
    }
    set mastRotation(mastRotation) {
        this.sensorCalibrationData[this.sensorSection]['mastrotationenabled'] = (mastRotation == true);
    }

    get mastRotationSource() {
        return this.mastRotationSources.find(source => (source.internalName == this.sensorCalibrationData[this.sensorSection]['mastrotationsource']))
            || new DataSource('External', 'ATProcessor:0');
    }
    set mastRotationSource(mastRotationSource) {
        this.sensorCalibrationData[this.sensorSection]['mastrotationsource'] = mastRotationSource;
    }

    get pulseCalibration() {
        return this.sensorCalibrationData[this.sensorSection]['pulsefreq'];
    }
    set pulseCalibration(pulseCalibration) {
        this.sensorCalibrationData[this.sensorSection]['pulsefreq'] = pulseCalibration;
    }

    get speedOffset() {
        return this.sensorCalibrationData[this.sensorSection]['speedoffset'];
    }
    set speedOffset(speedOffset) {
        this.sensorCalibrationData[this.sensorSection]['speedoffset'] = speedOffset;
    }

    get windShear() {
        return this.sensorCalibrationData[this.sensorSection]['windshear'];
    }
    set windShear(windShear) {
        this.sensorCalibrationData[this.sensorSection]['windshear'] = windShear;
    }

    getSensorData() {
        var async = new Array();
        var deferred = new $.Deferred();
        var self = this;
        var sensorData = new Object();

        var defaults = {
            'calibrationfactor': '1.0'
        }
        sensorData[self.sensorSection] = $.extend({}, defaults);

        async.push(
            super.getSensorData().done(function(data) {
                $.extend(true, sensorData, data);

                // Set sensorData[self.sensorSection].calibrationfactor to a default value,
                // if there is no value stored in the database.
                if (sensorData[self.sensorSection].calibrationfactor.length == 0)
                    sensorData[self.sensorSection].calibrationfactor = defaults.calibrationfactor;
            })
        );
        async.push(
            readSingle("Wind Calibration", "Time").done(function(estimator) {
                sensorData['Wind Calibration'] = { 'Time': estimator };
            })
        );
        async.push(
            readSingle("Hardware", "SupportsGyroData").done(function(supported) {
                self.supportsGyroData = (supported == 'true');
            })
        );

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

        return deferred.promise();
    }

    get mastRotationSources() {
        var mastRotationSources = new Array();

        this.analogueSources.forEach(function(dataSource) {
            dataSource.inputs.forEach(function(channel) {
                if (channel.sourceData['function'] == 2) {
                    mastRotationSources.push(channel);
                }
            });
        });

        mastRotationSources.sort(function(a, b) {
            return (a.internalName > b.internalName) ? 1 : -1;
        });

        return mastRotationSources;
    }

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

        var defaults = {
            'angleoffset': '0.0',
            'calibrationfactor': '1',
            'heelanglecorrenabled': '',
            'heelspeedcorrenabled': '',
            'pulsefreq': '0.98',
            'speedoffset': '0.76',
            'mastrotationenabled': '',
            'mastrotationsource': '',
            'mastmotionenabled': '',
            'mastheight': '45',
            'windshear': '0.0'
        }
        this.sensorCalibrationData[this.sensorSection] = $.extend({}, this.sensorCalibrationData[this.sensorSection], defaults);

        //Query MHU error field
        setInterval(function() {
            if (self.activeSource.internalName.split(':')[0].toLowerCase() == 'wind board') {
                $.ajax({
                    type: 'POST',
                    url: '/helpers.php',
                    data: {
                        action: 'queryProcessor',
                        query: 'FieldID=0x0336&SenderID=Wind Board&SenderInstance=' + self.sensorInstance
                    },
                    success: function(result) {
                        if (parseInt(result.split('=')[1]) > 0) {
                            $('[name="MHUPhase"]').css('border-color','red');
                        } else {
                            $('[name="MHUPhase"]').css('border-color','');
                        }
                    }
                });
            }
        }, 1000);

        getSources("Analogue").done(function(analogueSources, canbusDevices) {
            self.analogueSources = analogueSources;
            init.call(self).done(function() {
                deferred.resolve();
            })
        });

        return deferred.promise();
    }

    persistData() {
        return super.persistData("WindData");
    }

    setDrawableWidgets() {
        switch (this.sensorSource.toLowerCase()) {
            case dataSouceDisplayNames.CANbus.toLowerCase():
                this.widgets['displayCANbusDevice'] = true;
                this.widgets['displayNonPulseCalibration'] = true;
                this.widgets['displayMeasuredData'] = true;
                this.widgets['displayHeelCorrection'] = true;
                break;
            case dataSouceDisplayNames.NMEA.toLowerCase():
                this.widgets['displayNMEAPortSelection'] = true;
                this.widgets['displayNonPulseCalibration'] = true;
                this.widgets['displayMeasuredData'] = true;
                this.widgets['displayHeelCorrection'] = true;
                break;
            case dataSouceDisplayNames.NoSource.toLowerCase():
                break;
            default:
                this.widgets['displayPulseCalibration'] = true;
                this.widgets['displayMeasuredData'] = true;
                this.widgets['displayHeelCorrection'] = true;
                this.widgets['displaySpeedOffset'] = true;
                break;
        }
        this.widgets['displaySourceSelection'] = true;
        this.widgets['displayButtonBox'] = true;

        if (this.supportsGyroData) {
            this.widgets['displayMastMotion'] = true;
        }
    }
}