'use strict';

var colorbarAttrs = require('../colorbar/attributes');
var counterRegex = require('../../lib/regex').counter;
var sortObjectKeys = require('../../lib/sort_object_keys');

var palettes = require('./scales.js').scales;
var paletteStr = sortObjectKeys(palettes);

function code(s) {
    return '`' + s + '`';
}

/**
 * Make colorscale attribute declarations for
 *
 * - colorscale,
 * - (c|z)auto, (c|z)min, (c|z)max,
 * - autocolorscale, reversescale,
 * - showscale (optionally)
 * - color (optionally)
 *
 * @param {string} context (dflt: '', i.e. from trace root):
 *     the container this is in ('', *marker*, *marker.line* etc)
 *
 * @param {object} opts:
 *   - cLetter {string} (dflt: 'c'):
 *     leading letter for 'min', 'max and 'auto' attribute (either 'z' or 'c')
 *
 *   - colorAttr {string} (dflt: 'z' if `cLetter: 'z'`, 'color' if `cLetter: 'c'`):
 *     (for descriptions) sets the name of the color attribute that maps to the colorscale.
 *
 *     N.B. if `colorAttr: 'color'`, we include the `color` declaration here.
 *
 *   - onlyIfNumerical {string} (dflt: false' if `cLetter: 'z'`, true if `cLetter: 'c'`):
 *     (for descriptions) set to true if colorscale attribute only
 *
 *   - colorscaleDflt {string}:
 *     overrides the colorscale dflt
 *
 *   - autoColorDflt {boolean} (dflt true):
 *     normally autocolorscale.dflt is `true`, but pass `false` to override
 *
 *   - noScale {boolean} (dflt: true if `context: 'marker.line'`, false otherwise):
 *     set to `false` to not include showscale attribute (e.g. for 'marker.line')
 *
 *   - showScaleDflt {boolean} (dflt: true if `cLetter: 'z'`, false otherwise)
 *
 *   - editTypeOverride {boolean} (dflt: ''):
 *     most of these attributes already require a recalc, but the ones that do not
 *     have editType *style* or *plot* unless you override (presumably with *calc*)
 *
 *   - anim {boolean) (dflt: undefined): is 'color' animatable?
 *
 * @return {object}
 */
module.exports = function colorScaleAttrs(context, opts) {
    context = context || '';
    opts = opts || {};

    var cLetter = opts.cLetter || 'c';
    var onlyIfNumerical = ('onlyIfNumerical' in opts) ? opts.onlyIfNumerical : Boolean(context);
    var noScale = ('noScale' in opts) ? opts.noScale : context === 'marker.line';
    var showScaleDflt = ('showScaleDflt' in opts) ? opts.showScaleDflt : cLetter === 'z';
    var colorscaleDflt = typeof opts.colorscaleDflt === 'string' ? palettes[opts.colorscaleDflt] : null;
    var editTypeOverride = opts.editTypeOverride || '';
    var contextHead = context ? (context + '.') : '';

    var colorAttr, colorAttrFull;

    if('colorAttr' in opts) {
        colorAttr = opts.colorAttr;
        colorAttrFull = opts.colorAttr;
    } else {
        colorAttr = {z: 'z', c: 'color'}[cLetter];
        colorAttrFull = 'in ' + code(contextHead + colorAttr);
    }

    var effectDesc = onlyIfNumerical ?
        ' Has an effect only if ' + colorAttrFull + ' is set to a numerical array.' :
        '';

    var auto = cLetter + 'auto';
    var min = cLetter + 'min';
    var max = cLetter + 'max';
    var mid = cLetter + 'mid';
    var autoFull = code(contextHead + auto);
    var minFull = code(contextHead + min);
    var maxFull = code(contextHead + max);
    var minmaxFull = minFull + ' and ' + maxFull;
    var autoImpliedEdits = {};
    autoImpliedEdits[min] = autoImpliedEdits[max] = undefined;
    var minmaxImpliedEdits = {};
    minmaxImpliedEdits[auto] = false;

    var attrs = {};

    if(colorAttr === 'color') {
        attrs.color = {
            valType: 'color',
            arrayOk: true,
            editType: editTypeOverride || 'style',
            description: [
                'Sets the', context, 'color.',
                'It accepts either a specific color',
                'or an array of numbers that are mapped to the colorscale',
                'relative to the max and min values of the array or relative to',
                minmaxFull, 'if set.'
            ].join(' ')
        };

        if(opts.anim) {
            attrs.color.anim = true;
        }
    }

    attrs[auto] = {
        valType: 'boolean',
        dflt: true,
        editType: 'calc',
        impliedEdits: autoImpliedEdits,
        description: [
            'Determines whether or not the color domain is computed',
            'with respect to the input data (here ' + colorAttrFull + ') or the bounds set in',
            minmaxFull + effectDesc,
            'Defaults to `false` when', minmaxFull, 'are set by the user.'
        ].join(' ')
    };

    attrs[min] = {
        valType: 'number',
        dflt: null,
        editType: editTypeOverride || 'plot',
        impliedEdits: minmaxImpliedEdits,
        description: [
            'Sets the lower bound of the color domain.' + effectDesc,
            'Value should have the same units as', colorAttrFull,
            'and if set,', maxFull, 'must be set as well.'
        ].join(' ')
    };

    attrs[max] = {
        valType: 'number',
        dflt: null,
        editType: editTypeOverride || 'plot',
        impliedEdits: minmaxImpliedEdits,
        description: [
            'Sets the upper bound of the color domain.' + effectDesc,
            'Value should have the same units as', colorAttrFull,
            'and if set,', minFull, 'must be set as well.'
        ].join(' ')
    };

    attrs[mid] = {
        valType: 'number',
        dflt: null,
        editType: 'calc',
        impliedEdits: autoImpliedEdits,
        description: [
            'Sets the mid-point of the color domain by scaling', minFull,
            'and/or', maxFull, 'to be equidistant to this point.' + effectDesc,
            'Value should have the same units as', colorAttrFull + '.',
            'Has no effect when', autoFull, 'is `false`.'
        ].join(' ')
    };

    attrs.colorscale = {
        valType: 'colorscale',
        editType: 'calc',
        dflt: colorscaleDflt,
        impliedEdits: {autocolorscale: false},
        description: [
            'Sets the colorscale.' + effectDesc,
            'The colorscale must be an array containing',
            'arrays mapping a normalized value to an',
            'rgb, rgba, hex, hsl, hsv, or named color string.',
            'At minimum, a mapping for the lowest (0) and highest (1)',
            'values are required. For example,',
            '`[[0, \'rgb(0,0,255)\'], [1, \'rgb(255,0,0)\']]`.',
            'To control the bounds of the colorscale in color space,',
            'use', minmaxFull + '.',
            'Alternatively, `colorscale` may be a palette name string',
            'of the following list: ' + paletteStr + '.'
        ].join(' ')
    };

    attrs.autocolorscale = {
        valType: 'boolean',
        // gets overrode in 'heatmap' & 'surface' for backwards comp.
        dflt: opts.autoColorDflt === false ? false : true,
        editType: 'calc',
        impliedEdits: {colorscale: undefined},
        description: [
            'Determines whether the colorscale is a default palette (`autocolorscale: true`)',
            'or the palette determined by', code(contextHead + 'colorscale') + '.' + effectDesc,
            'In case `colorscale` is unspecified or `autocolorscale` is true, the default',
            'palette will be chosen according to whether numbers in the `color` array are',
            'all positive, all negative or mixed.'
        ].join(' ')
    };

    attrs.reversescale = {
        valType: 'boolean',
        dflt: false,
        editType: 'plot',
        description: [
            'Reverses the color mapping if true.' + effectDesc,
            'If true,', minFull, 'will correspond to the last color',
            'in the array and', maxFull, 'will correspond to the first color.'
        ].join(' ')
    };

    if(!noScale) {
        attrs.showscale = {
            valType: 'boolean',
            dflt: showScaleDflt,
            editType: 'calc',
            description: [
                'Determines whether or not a colorbar is displayed for this trace.' + effectDesc
            ].join(' ')
        };

        attrs.colorbar = colorbarAttrs;
    }

    if(!opts.noColorAxis) {
        attrs.coloraxis = {
            valType: 'subplotid',
            regex: counterRegex('coloraxis'),
            dflt: null,
            editType: 'calc',
            description: [
                'Sets a reference to a shared color axis.',
                'References to these shared color axes are *coloraxis*, *coloraxis2*, *coloraxis3*, etc.',
                'Settings for these shared color axes are set in the layout, under',
                '`layout.coloraxis`, `layout.coloraxis2`, etc.',
                'Note that multiple color scales can be linked to the same color axis.'
            ].join(' ')
        };
    }

    return attrs;
};
