$(function() {
    var cleanProfile = function() {
        return {
            id: "",
            name: "",
            model: "",
            color: "default",
            volume: {
                formFactor: "rectangular",
                width: 200,
                depth: 200,
                height: 200,
                origin: "lowerleft",
                custom_box: false
            },
            heatedBed: true,
            heatedChamber: false,
            axes: {
                x: {speed: 6000, inverted: false},
                y: {speed: 6000, inverted: false},
                z: {speed: 200, inverted: false},
                e: {speed: 300, inverted: false}
            },
            extruder: {
                count: 1,
                offsets: [
                    [0,0]
                ],
                nozzleDiameter: 0.4,
                sharedNozzle: false
            }
        }
    };

    function EditedProfileViewModel(profiles) {
        var self = this;

        self.profiles = profiles;

        self.isNew = ko.observable(false);

        self.name = ko.observable();
        self.color = ko.observable();
        self.identifier = ko.observable();
        self.identifierPlaceholder = ko.observable();
        self.model = ko.observable();

        self.volumeWidth = ko.observable();
        self.volumeHeight = ko.observable();
        self.volumeDepth = ko.observable();
        self.volumeFormFactor = ko.observable();
        self.volumeOrigin = ko.observable();

        self.volumeFormFactor.subscribe(function(value) {
            if (value == "circular") {
                self.volumeOrigin("center");
            }
        });
        self.volumeOrigin.subscribe(function() {
            self.toBoundingBoxPlaceholders(self.defaultBoundingBox(self.volumeWidth(), self.volumeDepth(), self.volumeHeight(), self.volumeOrigin()));
        });

        self.heatedBed = ko.observable();
        self.heatedChamber = ko.observable();

        self.nozzleDiameter = ko.observable();
        self.extruders = ko.observable();
        self.extruderOffsets = ko.observableArray();
        self.sharedNozzle = ko.observable();

        self.axisXSpeed = ko.observable();
        self.axisYSpeed = ko.observable();
        self.axisZSpeed = ko.observable();
        self.axisESpeed = ko.observable();

        self.axisXInverted = ko.observable(false);
        self.axisYInverted = ko.observable(false);
        self.axisZInverted = ko.observable(false);
        self.axisEInverted = ko.observable(false);

        self.customBoundingBox = ko.observable(false);
        self.boundingBoxMinX = ko.observable();
        self.boundingBoxMinY = ko.observable();
        self.boundingBoxMinZ = ko.observable();
        self.boundingBoxMaxX = ko.observable();
        self.boundingBoxMaxY = ko.observable();
        self.boundingBoxMaxZ = ko.observable();
        self.boundingBoxMinXPlaceholder = ko.observable();
        self.boundingBoxMinYPlaceholder = ko.observable();
        self.boundingBoxMinZPlaceholder = ko.observable();
        self.boundingBoxMaxXPlaceholder = ko.observable();
        self.boundingBoxMaxYPlaceholder = ko.observable();
        self.boundingBoxMaxZPlaceholder = ko.observable();

        self.koExtruderOffsets = ko.pureComputed(function() {
            var extruderOffsets = self.extruderOffsets();
            var numExtruders = self.extruders();
            if (!numExtruders) {
                numExtruders = 1;
            }

            if (numExtruders - 1 > extruderOffsets.length) {
                for (var i = extruderOffsets.length; i < numExtruders; i++) {
                    extruderOffsets[i] = {
                        idx: i + 1,
                        x: ko.observable(0),
                        y: ko.observable(0)
                    }
                }
                self.extruderOffsets(extruderOffsets);
            }

            return extruderOffsets.slice(0, numExtruders - 1);
        });

        self.nameInvalid = ko.pureComputed(function() {
            return !self.name();
        });

        self.identifierInvalid = ko.pureComputed(function() {
            var identifier = self.identifier();
            var placeholder = self.identifierPlaceholder();
            var data = identifier;
            if (!identifier) {
                data = placeholder;
            }

            var validCharacters = (data && (data == self._sanitize(data)));

            var existingProfile = self.profiles.getItem(function(item) {return item.id == data});
            return !data || !validCharacters || (self.isNew() && existingProfile != undefined);
        });

        self.identifierInvalidText = ko.pureComputed(function() {
            if (!self.identifierInvalid()) {
                return "";
            }

            if (!self.identifier() && !self.identifierPlaceholder()) {
                return gettext("Identifier must be set");
            } else if (self.identifier() != self._sanitize(self.identifier())) {
                return gettext("Invalid characters, only a-z, A-Z, 0-9, -, ., _, ( and ) are allowed")
            } else {
                return gettext("A profile with such an identifier already exists");
            }
        });

        self.name.subscribe(function() {
            self.identifierPlaceholder(self._sanitize(self.name()).toLowerCase());
        });

        self.valid = function() {
            return !self.nameInvalid() && !self.identifierInvalid();
        };

        self.availableColors = ko.observable([
            {key: "default", name: gettext("default")},
            {key: "red", name: gettext("red")},
            {key: "orange", name: gettext("orange")},
            {key: "yellow", name: gettext("yellow")},
            {key: "green", name: gettext("green")},
            {key: "blue", name: gettext("blue")},
            {key: "black", name: gettext("black")}
        ]);

        self.availableOrigins = ko.pureComputed(function() {
            var formFactor = self.volumeFormFactor();

            var possibleOrigins = {
                "lowerleft": gettext("Lower Left"),
                "center": gettext("Center")
            };

            var keys = [];
            if (formFactor == "rectangular") {
                keys = ["lowerleft", "center"];
            } else if (formFactor == "circular") {
                keys = ["center"];
            }

            var result = [];
            _.each(keys, function(key) {
               result.push({key: key, name: possibleOrigins[key]});
            });
            return result;
        });

        self.fromProfileData = function(data) {
            self.isNew(data === undefined);

            if (data === undefined) {
                data = cleanProfile();
            }

            self.identifier(data.id);
            self.name(data.name);
            self.color(data.color);
            self.model(data.model);

            self.volumeWidth(data.volume.width);
            self.volumeHeight(data.volume.height);
            self.volumeDepth(data.volume.depth);
            self.volumeFormFactor(data.volume.formFactor);
            self.volumeOrigin(data.volume.origin);

            if (data.volume.custom_box) {
                self.toBoundingBoxData(data.volume.custom_box, true);
            } else {
                var box = self.defaultBoundingBox(data.volume.width, data.volume.depth, data.volume.height, data.volume.origin);
                self.toBoundingBoxData(box, false);
            }

            self.heatedBed(data.heatedBed);
            self.heatedChamber(data.heatedChamber);

            self.nozzleDiameter(data.extruder.nozzleDiameter);
            self.sharedNozzle(data.extruder.sharedNozzle);
            self.extruders(data.extruder.count);
            var offsets = [];
            if (data.extruder.count > 1) {
                _.each(_.slice(data.extruder.offsets, 1), function(offset, index) {
                    offsets.push({
                        idx: index + 1,
                        x: ko.observable(offset[0]),
                        y: ko.observable(offset[1])
                    });
                });
            }
            self.extruderOffsets(offsets);

            self.axisXSpeed(data.axes.x.speed);
            self.axisXInverted(data.axes.x.inverted);
            self.axisYSpeed(data.axes.y.speed);
            self.axisYInverted(data.axes.y.inverted);
            self.axisZSpeed(data.axes.z.speed);
            self.axisZInverted(data.axes.z.inverted);
            self.axisESpeed(data.axes.e.speed);
            self.axisEInverted(data.axes.e.inverted);
        };

        self.toProfileData = function() {
            var identifier = self.identifier();
            if (!identifier) {
                identifier = self.identifierPlaceholder();
            }

            var defaultProfile = cleanProfile();
            var valid = function(value, f, def) {
                var v = f(value);
                if (isNaN(v)) {
                    return def;
                }
                return v;
            };
            var validFloat = function(value, def) {
                return valid(value, parseFloat, def);
            };
            var validInt = function(value, def) {
                return valid(value, parseInt, def);
            };

            var profile = {
                id: identifier,
                name: self.name(),
                color: self.color(),
                model: self.model(),
                volume: {
                    width: validFloat(self.volumeWidth(), defaultProfile.volume.width),
                    depth: validFloat(self.volumeDepth(), defaultProfile.volume.depth),
                    height: validFloat(self.volumeHeight(), defaultProfile.volume.height),
                    formFactor: self.volumeFormFactor(),
                    origin: self.volumeOrigin()
                },
                heatedBed: self.heatedBed(),
                heatedChamber: self.heatedChamber(),
                extruder: {
                    count: parseInt(self.extruders()),
                    offsets: [
                        [0.0, 0.0]
                    ],
                    nozzleDiameter: validFloat(self.nozzleDiameter(), defaultProfile.extruder.nozzleDiameter),
                    sharedNozzle: self.sharedNozzle()
                },
                axes: {
                    x: {
                        speed: validInt(self.axisXSpeed(), defaultProfile.axes.x.speed),
                        inverted: self.axisXInverted()
                    },
                    y: {
                        speed: validInt(self.axisYSpeed(), defaultProfile.axes.y.speed),
                        inverted: self.axisYInverted()
                    },
                    z: {
                        speed: validInt(self.axisZSpeed(), defaultProfile.axes.z.speed),
                        inverted: self.axisZInverted()
                    },
                    e: {
                        speed: validInt(self.axisESpeed(), defaultProfile.axes.e.speed),
                        inverted: self.axisEInverted()
                    }
                }
            };

            self.fillBoundingBoxData(profile);

            var offsetX, offsetY;
            if (self.extruders() > 1) {
                for (var i = 0; i < self.extruders() - 1; i++) {
                    var offset = [0.0, 0.0];
                    if (i < self.extruderOffsets().length) {
                        offsetX = validFloat(self.extruderOffsets()[i]["x"](), 0.0);
                        offsetY = validFloat(self.extruderOffsets()[i]["y"](), 0.0);
                        offset = [offsetX, offsetY];
                    }
                    profile.extruder.offsets.push(offset);
                }
            }

            if (profile.volume.formFactor == "circular") {
                profile.volume.depth = profile.volume.width;
            }

            return profile;
        };

        self.defaultBoundingBox = function(width, depth, height, origin) {
            if (origin == "center") {
                var halfWidth = width / 2.0;
                var halfDepth = depth / 2.0;

                return {
                    x_min: -halfWidth,
                    y_min: -halfDepth,
                    z_min: 0.0,
                    x_max: halfWidth,
                    y_max: halfDepth,
                    z_max: height
                }
            } else {
                return {
                    x_min: 0.0,
                    y_min: 0.0,
                    z_min: 0.0,
                    x_max: width,
                    y_max: depth,
                    z_max: height
                }
            }
        };

        self.toBoundingBoxData = function(box, custom) {
            self.customBoundingBox(custom);
            if (custom) {
                self.boundingBoxMinX(box.x_min);
                self.boundingBoxMinY(box.y_min);
                self.boundingBoxMinZ(box.z_min);
                self.boundingBoxMaxX(box.x_max);
                self.boundingBoxMaxY(box.y_max);
                self.boundingBoxMaxZ(box.z_max);
            } else {
                self.boundingBoxMinX(undefined);
                self.boundingBoxMinY(undefined);
                self.boundingBoxMinZ(undefined);
                self.boundingBoxMaxX(undefined);
                self.boundingBoxMaxY(undefined);
                self.boundingBoxMaxZ(undefined);
            }
            self.toBoundingBoxPlaceholders(box);
        };

        self.toBoundingBoxPlaceholders = function(box) {
            self.boundingBoxMinXPlaceholder(box.x_min);
            self.boundingBoxMinYPlaceholder(box.y_min);
            self.boundingBoxMinZPlaceholder(box.z_min);
            self.boundingBoxMaxXPlaceholder(box.x_max);
            self.boundingBoxMaxYPlaceholder(box.y_max);
            self.boundingBoxMaxZPlaceholder(box.z_max);
        };

        self.fillBoundingBoxData = function(profile) {
            if (self.customBoundingBox()) {
                var defaultBox = self.defaultBoundingBox(self.volumeWidth(), self.volumeDepth(), self.volumeHeight(), self.volumeOrigin());
                profile.volume.custom_box = {
                    x_min: (self.boundingBoxMinX() !== undefined) ? Math.min(self.boundingBoxMinX(), defaultBox.x_min) : defaultBox.x_min,
                    y_min: (self.boundingBoxMinY() !== undefined) ? Math.min(self.boundingBoxMinY(), defaultBox.y_min) : defaultBox.y_min,
                    z_min: (self.boundingBoxMinZ() !== undefined) ? Math.min(self.boundingBoxMinZ(), defaultBox.z_min) : defaultBox.z_min,
                    x_max: (self.boundingBoxMaxX() !== undefined) ? Math.max(self.boundingBoxMaxX(), defaultBox.x_max) : defaultBox.x_max,
                    y_max: (self.boundingBoxMaxY() !== undefined) ? Math.max(self.boundingBoxMaxY(), defaultBox.y_max) : defaultBox.y_max,
                    z_max: (self.boundingBoxMaxZ() !== undefined) ? Math.max(self.boundingBoxMaxZ(), defaultBox.z_max) : defaultBox.z_max
                };
            } else {
                profile.volume.custom_box = false;
            }
        };

        self._sanitize = function(name) {
            return name.replace(/[^a-zA-Z0-9\-_\.\(\) ]/g, "").replace(/ /g, "_");
        };

        self.fromProfileData(cleanProfile());
    }

    function PrinterProfilesViewModel(parameters) {
        var self = this;

        self.loginState = parameters[0];
        self.access = parameters[1];

        self.requestInProgress = ko.observable(false);

        self.profiles = new ItemListHelper(
            "printerProfiles",
            {
                "name": function(a, b) {
                    // sorts ascending
                    if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
                    if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
                    return 0;
                }
            },
            {},
            "name",
            [],
            [],
            10
        );
        self.defaultProfile = ko.observable();
        self.currentProfile = ko.observable();

        self.createProfileEditor = function(data) {
            var editor = new EditedProfileViewModel(self.profiles);
            if (data !== undefined) {
                editor.fromProfileData(data);
            }
            return editor;
        };

        self.editor = self.createProfileEditor();
        self.currentProfileData = ko.observable(ko.mapping.fromJS(cleanProfile()));

        self.enableEditorSubmitButton = ko.pureComputed(function() {
            return self.editor.valid() && !self.requestInProgress();
        });

        self.makeDefault = function(data) {
            var profile = {
                id: data.id,
                default: true
            };

            self.updateProfile(profile);
        };

        self.canMakeDefault = function(data) {
            return !data.isdefault();
        };

        self.canRemove = function(data) {
            return !data.iscurrent() && !data.isdefault();
        };

        self.requestData = function() {
            if (!self.loginState.hasPermission(self.access.permissions.CONNECTION)) {
                return;
            }

            return OctoPrint.printerprofiles.list()
                .done(self.fromResponse);
        };

        self.fromResponse = function(data) {
            var items = [];
            var defaultProfile = undefined;
            var currentProfile = undefined;
            var currentProfileData = undefined;
            _.each(data.profiles, function(entry) {
                if (entry.default) {
                    defaultProfile = entry.id;
                }
                if (entry.current) {
                    currentProfile = entry.id;
                    currentProfileData = ko.mapping.fromJS(entry, self.currentProfileData);
                }
                entry["isdefault"] = ko.observable(entry.default);
                entry["iscurrent"] = ko.observable(entry.current);
                items.push(entry);
            });
            self.profiles.updateItems(items);
            self.defaultProfile(defaultProfile);

            if (currentProfile && currentProfileData) {
                self.currentProfile(currentProfile);
                self.currentProfileData(currentProfileData);
            } else {
                // shouldn't normally happen, but just to not have anything else crash...
                log.warn("Current printer profile could not be detected, using default values");
                self.currentProfile("");
                self.currentProfileData(ko.mapping.fromJS(cleanProfile(), self.currentProfileData));
            }
        };

        self.addProfile = function(callback) {
            var profile = self.editor.toProfileData();
            self.requestInProgress(true);
            OctoPrint.printerprofiles.add(profile)
                .done(function() {
                    if (callback !== undefined) {
                        callback();
                    }
                    self.requestData();
                })
                .fail(function(xhr) {
                    var text = gettext("There was unexpected error while saving the printer profile, please consult the logs.");
                    new PNotify({title: gettext("Could not add profile"), text: text, type: "error", hide: false});
                })
                .always(function() {
                    self.requestInProgress(false);
                });
        };

        self.removeProfile = function(data) {
            var perform = function() {
                self.requestInProgress(true);
                OctoPrint.printerprofiles.delete(data.id, {url: data.resource})
                    .done(function() {
                        self.requestData()
                            .always(function() {
                                self.requestInProgress(false);
                            });
                    })
                    .fail(function(xhr) {
                        var text;
                        if (xhr.status == 409) {
                            text = gettext("Cannot delete the default profile or the currently active profile.");
                        } else {
                            text = gettext("There was unexpected error while removing the printer profile, please consult the logs.");
                        }
                        new PNotify({title: gettext("Could not delete profile"), text: text, type: "error", hide: false});
                        self.requestInProgress(false);
                    });
            };

            showConfirmationDialog(_.sprintf(gettext("You are about to delete the printer profile \"%(name)s\"."), {name: _.escape(data.name)}),
                                   perform);
        };

        self.updateProfile = function(profile, callback) {
            if (profile == undefined) {
                profile = self.editor.toProfileData();
            }

            self.requestInProgress(true);
            OctoPrint.printerprofiles.update(profile.id, profile)
                .done(function() {
                    if (callback !== undefined) {
                        callback();
                    }
                    self.requestData()
                        .always(function() {
                            self.requestInProgress(false);
                        });
                })
                .fail(function() {
                    var text = gettext("There was unexpected error while updating the printer profile, please consult the logs.");
                    new PNotify({title: gettext("Could not update profile"), text: text, type: "error", hide: false});
                    self.requestInProgress(false);
                });
        };

        self.showEditProfileDialog = function(data) {
            self.editor.fromProfileData(data);

            var editDialog = $("#settings_printerProfiles_editDialog");
            var confirmButton = $("button.btn-confirm", editDialog);
            var dialogTitle = $("h3.modal-title", editDialog);

            var add = data === undefined;
            dialogTitle.text(add ? gettext("Add Printer Profile") : _.sprintf(gettext("Edit Printer Profile \"%(name)s\""), {name: _.escape(data.name)}));
            confirmButton.unbind("click");
            confirmButton.bind("click", function() {
                if (self.enableEditorSubmitButton()) {
                    self.confirmEditProfile(add);
                }
            });

            $('ul.nav-pills a[data-toggle="tab"]:first', editDialog).tab("show");
            editDialog.modal({
                minHeight: function() { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); }
            }).css({
                width: 'auto',
                'margin-left': function() { return -($(this).width() /2); }
            });
        };

        self.confirmEditProfile = function(add) {
            var callback = function() {
                $("#settings_printerProfiles_editDialog").modal("hide");
            };

            if (add) {
                self.addProfile(callback);
            } else {
                self.updateProfile(undefined, callback);
            }
        };

        self.onSettingsShown = self.requestData;

        self.onUserPermissionsChanged = self.onUserLoggedIn = self.onUserLoggedOut = function() {
            self.requestData();
        }
    }

    OCTOPRINT_VIEWMODELS.push({
        construct: PrinterProfilesViewModel,
        dependencies: ["loginStateViewModel", "accessViewModel"]
    });
});
