# Source: src/js/models/maps/AssetColorPalette.js

    'use strict';

    define(
      [
        'jquery',
        'underscore',
        'backbone',
        'collections/maps/AssetColors'
      ],
      function (
        $,
        _,
        Backbone,
        AssetColors
      ) {
        /**
         * @classdesc An AssetColorPalette Model represents a color scale that is mapped to
         * some attribute of a Map Asset. For vector assets, like 3D tilesets, this palette is
         * used to conditionally color features on a map. For any type of asset, it can be
         * used to generate a legend.
         * @classcategory Models/Maps
         * @class AssetColorPalette
         * @name AssetColorPalette
         * @extends Backbone.Model
         * @since 2.18.0
         * @constructor
        */
        var AssetColorPalette = Backbone.Model.extend(
          /** @lends AssetColorPalette.prototype */ {

            /**
             * The name of this type of model
             * @type {string}
            */
            type: 'AssetColorPalette',

            /**
             * Default attributes for AssetColorPalette models
             * @name AssetColorPalette#defaults
             * @type {Object}
             * @property {('categorical'|'continuous'|'classified')}
             * [paletteType='categorical'] Set to 'categorical', 'continuous', or
             * 'classified'. NOTE: Currently only categorical and continuous palettes are
             * supported.
             * - Categorical: the color conditions will be interpreted such that one color
             *   represents a single value (e.g. a discrete palette).
             * - Continuous: each color in the colors attribute will represent a point in a
             *   gradient. The point in the gradient will be associated with the number set
             *   with the color, and numbers in between points will be set to an interpolated
             *   color.
             * - Classified: the numbers set in the colors attribute will be interpreted as
             *   maximums. Continuous properties will be forced into discrete bins.
             * @property {string} property The name (ID) of the property in the asset layer's
             * attribute table to color the vector data by (or for imagery data that does not
             * have an attribute table, just the name of the attribute that these colors map
             * to).
             * @property {string} [label = null] A user-friendly name to display instead of
             * the actual property name.
             * @property {AssetColors} [colors = new AssetColors()] The colors to use in the
             * color palette, along with the conditions associated with each color (i.e. the
             * properties of the feature that must be true to use the given color.) . The last
             * color in the collection will always be treated as the default color - any
             * feature that doesn't match the other colors will be colored with this color.
            */
            defaults: function () {
              return {
                paletteType: 'categorical',
                property: null,
                label: null,
                colors: new AssetColors()
              }
            },

            /**
             * The ColorPaletteConfig specifies a color scale that is mapped to some attribute
             * of a {@link MapAsset}. For vector assets, like 3D tilesets, this palette is
             * used to conditionally color features on a map. For any type of asset, including
             * imagery, it can be used to generate a legend. The ColorPaletteConfig is passed
             * to a {@link AssetColorPalette} model.
             * @typedef {Object} ColorPaletteConfig
             * @name MapConfig#ColorPaletteConfig
             * @property {('categorical'|'continuous'|'classified')}
             * [paletteType='categorical'] NOTE: Currently only categorical and continuous
             * palettes are supported.
             * - Categorical: the color conditions will be interpreted such that one color
             *   represents a single value (e.g. a discrete palette).
             * - Continuous: each color in the colors attribute will represent a point in a
             *   gradient. The point in the gradient will be associated with the number set
             *   with the color, and numbers in between points will be set to an interpolated
             *   color.
             * - Classified: the numbers set in the colors attribute will be interpreted as
             *   maximums. Continuous properties will be forced into discrete bins.
             * @property {string} property The name (ID) of the property in the asset layer's
             * attribute table to color the vector data by (or for imagery data that does not
             * have an attribute table, just the name of the attribute that these colors
             * represent).
             * @property {string} [label] A user-friendly name to display instead of the
             * actual property name.
             * @property {MapConfig#ColorConfig[]} colors The colors to use in the color
             * palette, along with the conditions associated with each color (i.e. the
             * properties of the feature that must be true to use the given color). The array
             * of ColorConfig objects are passed to a {@link AssetColors} collection, which in
             * turn passes each ColorConfig to a {@link AssetColor} model.
             *
             * @example
             * {
             *    paletteType: 'categorical',
             *    property: 'landUse',
             *    label: 'Land Use in 2016',
             *    colors: [
             *      { value: "agriculture", color: "#FF5733" },
             *      { value: "park", color: "#33FF80" }
             *    ]
             * }
            */

            /**
             * Executed when a new AssetColorPalette model is created.
             * @param {MapConfig#ColorPaletteConfig} [paletteConfig] The initial values of the
             * attributes, which will be set on the model.
             */
            initialize: function (paletteConfig) {
              try {
                if (paletteConfig && paletteConfig.colors) {
                  this.set('colors', new AssetColors(paletteConfig.colors))
                }
                // If a continuous palette has only 1 colour, then treat it as categorical
                if (this.get('paletteType') === 'continuous' && this.get('colors').length === 1) {
                  this.set('paletteType', 'categorical')
                }
              }
              catch (error) {
                console.log(
                  'There was an error initializing a AssetColorPalette model' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Given properties of a feature, returns the color associated with that feature.
             * @param {Object} properties The properties of the feature to get the color for.
             * (See the 'properties' attribute of {@link Feature#defaults}.)
             * @returns {AssetColor#Color} The color associated with the given set of
             * properties.
             */
            getColor: function (properties) {

              const colorPalette = this;

              // As a backup, use the default color
              const defaultColor = this.getDefaultColor();
              let color = defaultColor

              // The name of the property to conditionally color the features by
              const prop = colorPalette.get('property');
              // The value for the this property in the given properties
              const propValue = properties[prop];
              // Each palette type has different ways of getting the color
              const type = colorPalette.get('paletteType');
              // The collection of colors + conditions
              const colors = colorPalette.get('colors');

              if (!colors || colors.length === 0) {
                // Skip the other if statements, use default color.
              } else if (colors.length === 1) {
                // If there's just 1 color, then return that color.
                color = colors.at(0).get('color');
              } else if (type === 'categorical') {
                // For a categorical color palette, the value of the feature property just
                // needs to match one of the values in the list of color conditions.
                // If it matches, then return the color associated with that value.
                const colorMatch = colors.findWhere({ value: propValue });
                if (colorMatch) {
                  color = colorMatch.get('color');
                }
              } else if (type === 'classified') {
                // TODO: test

                // For a classified color palette, the value of the feature property needs to
                // be greater than or equal to the value of the color condition. Use a
                // sorted array.

                // const sortedColors = colors.toArray().sort(function (a, b) {
                //   return a.get('value') - b.get('value')
                // })
                // let i = 0;
                // while (i < sortedColors.length && propValue >= sortedColors[i].get('value')) {
                //   i++;
                // }
                // color = sortedColors[i].get('color');
              } else if (type === 'continuous') {
                // For a continuous color palette, the value of the feature property must
                // either match one of the values in the color palette, or be interpolated
                // between the values in the palette.
                const sortedColors = colors.toArray().sort(function (a, b) {
                  return a.get('value') - b.get('value')
                })
                let i = 0;
                while (i < sortedColors.length && propValue >= sortedColors[i].get('value')) {
                  i++;
                }
                if (i === 0) {
                  color = sortedColors[0].get('color');
                }
                else if (i === sortedColors.length) {
                  color = sortedColors[i - 1].get('color');
                }
                else {
                  const percent = (propValue - sortedColors[i - 1].get('value')) /
                    (sortedColors[i].get('value') - sortedColors[i - 1].get('value'));
                  color = colorPalette.interpolate(sortedColors[i - 1].get('color'), sortedColors[i].get('color'), percent)
                }
              }
              return color
            },

            /**
             * Given two colors, returns a color that is a linear interpolation between the
             * two colors.
             * @param {AssetColor#Color} color1 The first color.
             * @param {AssetColor#Color} color2 The second color.
             * @param {number} fraction The percentage of the way between the two colors, 0-1.
             * @returns {AssetColor#Color} The interpolated color.
             */
            interpolate: function (color1, color2, fraction) {
              const red = color1.red + fraction * (color2.red - color1.red)
              const green = color1.green + fraction * (color2.green - color1.green)
              const blue = color1.blue + fraction * (color2.blue - color1.blue)
              return {
                red: red,
                green: green,
                blue: blue
              }
            },

            /**
             * Gets the default color for the color palette, returns it as an object of RGB
             * intestines between 0 and 1.
             * @returns {AssetColor#Color} The default color for the palette.
             */
            getDefaultColor: function () {
              return this.get('colors').getDefaultColor().get('color');
            },

            // /**
            //  * Parses the given input into a JSON object to be set on the model.
            //  *
            //  * @param {TODO} input - The raw response object
            //  * @return {TODO} - The JSON object of all the AssetColorPalette attributes
            //  */
            // parse: function (input) {

            //   try {

            //     var modelJSON = {};

            //     return modelJSON

            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error parsing a AssetColorPalette model' +
            //       '. Error details: ' + error
            //     );
            //   }

            // },

            // /**
            //  * Overrides the default Backbone.Model.validate.function() to check if this if
            //  * the values set on this model are valid.
            //  *
            //  * @param {Object} [attrs] - A literal object of model attributes to validate.
            //  * @param {Object} [options] - A literal object of options for this validation
            //  * process
            //  *
            //  * @return {Object} - Returns a literal object with the invalid attributes and
            //  * their corresponding error message, if there are any. If there are no errors,
            //  * returns nothing.
            //  */
            // validate: function (attrs, options) {
            //   try {

            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error validating a AssetColorPalette model' +
            //       '. Error details: ' + error
            //     );
            //   }
            // },

            // /**
            //  * Creates a string using the values set on this model's attributes.
            //  * @return {string} The AssetColorPalette string
            //  */
            // serialize: function () {
            //   try {
            //     var serializedAssetColorPalette = '';

            //     return serializedAssetColorPalette;
            //   }
            //   catch (error) {
            //     console.log(
            //       'There was an error serializing a AssetColorPalette model' +
            //       '. Error details: ' + error
            //     );
            //   }
            // },

          });

        return AssetColorPalette;

      }
    );