/*global params, stringIDToTypeID, charIDToTypeID,
    ActionDescriptor, ActionList, executeAction, DialogModes */

// Required params:
//   - documentId: The ID of the document requested
//   - layerSpec: Either the layer ID of the desired layer as a number, or an object of the form
//         {firstLayerIndex: number, lastLayerIndex: number, hidden: Array.<number>=} specifying the
//         desired index range, inclusive, and (optionally) an array of indices to hide.
//         Note that the number form takes a layer ID, *not* a layer index.
//   - boundsOnly: Whether to only request the bounds fo the pixmap
//   Either use absolute scaling by specifying which part of the doc should be transformed into what shape:
//   - inputRect:  { left: ..., top: ..., right: ..., bottom: ... }
//   - outputRect: { left: ..., top: ..., right: ..., bottom: ... }
//   Or use relative scaling by specifying horizontal and vertical factors:
//   - scaleX:     The x-dimension scale factor (e.g. 0.5 for half size) for the output pixmap
//   - scaleY:     The y-dimension scale factor (e.g. 0.5 for half size) for the output pixmap
//
// Optional params:
//   - useSmartScaling: setting to "true" causes shapes to be scaled in the "smart" way, which (confusingly)
//         means that stroke effects (e.g. rounded rect corners) are *not* scaled. (Default: false)
//   - includeAncestorMasks: setting to "true" causes exported layer to be clipped by any ancestor
//         masks that are visible (Default: false)
//   - convertToWorkingRGBProfile: If true, performs a color conversion on the pixels
//         before they are sent to generator. The color is converted to the working RGB profile (specified for
//         the document in PS). By default (when this setting is false), the "raw" RGB data is sent, which is
//         what is usually desired. (Default: false)
//   - useICCProfile: String with the ICC color profile to use. If set this overrides
//         the convertToWorkingRGBProfile flag. A common value is "sRGB IEC61966-2.1". (Default: "")
//   - getICCProfileData: If true then the final ICC profile for the image is included 
//         along with the returned pixamp (added after PS 16.1)
//   - allowDither: controls whether any dithering could possibly happen in the color conversion
//         to 8-bit RGB. If false, then dithering will definitely not occur, regardless of either
//         the value of useColorSettingsDither and the color settings in Photoshop. (Default: false)
//   - useColorSettingsDither: If allowDither is true, then this controls whether to (if true) defer to
//         the user's color settings in PS, or (if false) to force dither in any case where a
//         conversion to 8-bit RGB would otherwise be lossy. If allowDither is false, then the
//         value of this parameter is ignored. (Default: false)
//   - interpolationType: Force pixmap scaling to use the given interpolation method.
//         If defined, the value should be one of the Generator.prototype.INTERPOLATION constants. Otherwise,
//         Photoshop's default interpolation type (as specified in Preferences > Image Interpolation) is used.
//         (Default: undefined)
//   - forceSmartPSDPixelScaling: If true, forces PSD Smart objects to be scaled completely in pixel space
//         (as opposed to scaling vectors, text, etc. in a smoother fashion.) In PS 15.0 and earlier
//         pixel space scaling was the only option. So, setting this to "true" will replicate older behavior
//         (Default: false)
//   - clipToDocumentBounds: If true, crops returned pixels to the document bounds.
//         By default, all pixels for the specified layers are returned, even if they lie outside the document
//         bounds (e.g. if the document was cropped without "Delete Cropped Pixels" checked).
//         Note that this option *cannot* be used with an inputRect/outputRect scaling. If inputRect/outputRect
//         is set, this setting will be ignored and the pixels will not be cropped to document bounds.
//         (Default: false)
//   - clipBounds: crops retuned pixels to the given bounds
//   - usePSClipping: if present use clipBounds to clip image
//   - compId: number, layer comp ID (optionally and exclusive of compIndex)
//   - compIndex: number, layer comp index (optionally and exclusive of compId) 
//   - maxDimension: number, maximal dimension of pixmap in pixels

var DEFAULT_MAX_DIMENSION = 10000;

var actionDescriptor = new ActionDescriptor(),
    transform = null;

// Add a transform if necessary
if (params.inputRect && params.outputRect) {
    transform = new ActionDescriptor();

    // The part of the document to use
    var inputRect   = params.inputRect,
        psInputRect = new ActionList();

    psInputRect.putUnitDouble(charIDToTypeID("#Pxl"), inputRect.left);
    psInputRect.putUnitDouble(charIDToTypeID("#Pxl"), inputRect.top);
    
    psInputRect.putUnitDouble(charIDToTypeID("#Pxl"), inputRect.right);
    psInputRect.putUnitDouble(charIDToTypeID("#Pxl"), inputRect.bottom);

    transform.putList(stringIDToTypeID("rectangle"), psInputRect);

    // Where to move the four corners
    var outputRect      = params.outputRect,
        psOutputCorners = new ActionList();

    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.left);
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.top);
    
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.right);
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.top);
    
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.right);
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.bottom);
    
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.left);
    psOutputCorners.putUnitDouble(charIDToTypeID("#Pxl"), outputRect.bottom);

    transform.putList(stringIDToTypeID("quadrilateral"), psOutputCorners);

    // Absolute scaling may not keep the aspect ratio intact, in which case effects
    // cannot be scaled. To be consistent, turn it off for all of absolute scaling
    // transform.putBoolean(stringIDToTypeID("scaleStyles"), false);

} else if (params.scaleX && params.scaleY && (params.scaleX !== 1 || params.scaleY !== 1)) {
    transform = new ActionDescriptor();

    if (!params.useSmartScaling) {
        transform.putBoolean(stringIDToTypeID("forceDumbScaling"), true);
    }

    transform.putDouble(charIDToTypeID("Wdth"), params.scaleX * 100);
    transform.putDouble(charIDToTypeID("Hght"), params.scaleY * 100);
}

if (transform) {
    // interpolation and scaling options are only relevant in cases where a transform
    // is going to happen. So, we only bother to set them if we're actually going
    // to add a transform descriptor

    if (!params.useSmartScaling) {
        transform.putBoolean(stringIDToTypeID("forceDumbScaling"), true);
    }

    if (params.hasOwnProperty("interpolationType")) {
        transform.putEnumerated(stringIDToTypeID("interpolation"),
                                stringIDToTypeID("interpolationType"),
                                stringIDToTypeID(params.interpolationType));

        actionDescriptor.putEnumerated(stringIDToTypeID("interpolation"),
                                stringIDToTypeID("interpolationType"),
                                stringIDToTypeID(params.interpolationType));
    }

    if (params.hasOwnProperty("forceSmartPSDPixelScaling")) {
        transform.putBoolean(stringIDToTypeID("forceSmartPSDPixelScaling"), !!params.forceSmartPSDPixelScaling);
    }

    // actually add the transform descriptor to the main descriptor
    actionDescriptor.putObject(stringIDToTypeID("transform"), stringIDToTypeID("transform"), transform);
}

actionDescriptor.putInteger(stringIDToTypeID("documentID"), params.documentId);
actionDescriptor.putInteger(stringIDToTypeID("width"), params.maxDimension || DEFAULT_MAX_DIMENSION);
actionDescriptor.putInteger(stringIDToTypeID("height"), params.maxDimension || DEFAULT_MAX_DIMENSION);
actionDescriptor.putInteger(stringIDToTypeID("format"), 2);

if (typeof(params.layerSpec) === "object") {
    actionDescriptor.putInteger(stringIDToTypeID("firstLayer"), params.layerSpec.firstLayerIndex);
    actionDescriptor.putInteger(stringIDToTypeID("lastLayer"), params.layerSpec.lastLayerIndex);
    if (params.layerSpec.hasOwnProperty("hidden") && params.layerSpec.hidden.length > 0) {
        var i,
            hiddenIndiciesMap = {},
            settingsList = new ActionList(),
            hiddenLayerDesc = new ActionDescriptor(),
            visibleLayerDesc = new ActionDescriptor(),
            hiddenLayerSettings = new ActionDescriptor(),
            lsID = stringIDToTypeID("layerSettings");

        hiddenLayerSettings.putBoolean(stringIDToTypeID("enabled"), false);
        hiddenLayerDesc.putObject(lsID, lsID, hiddenLayerSettings);

        // We have to add a descriptor for every layer in order, so first
        // build a map to make it easier to do this.
        for (i = 0; i < params.layerSpec.hidden.length; ++i) {
            hiddenIndiciesMap[params.layerSpec.hidden[i]] = true;
        }

        // Loop over every layer, and add either a hidden or visible descriptor
        // based on the map we built.
        for (i = params.layerSpec.firstLayerIndex; i <= params.layerSpec.lastLayerIndex; ++i) {
            if (hiddenIndiciesMap[i]) {
                settingsList.putObject(lsID, hiddenLayerDesc);
            } else {
                settingsList.putObject(lsID, visibleLayerDesc);
            }
        }

        actionDescriptor.putList(stringIDToTypeID("layerSettings"), settingsList);
    }

} else {
    actionDescriptor.putInteger(stringIDToTypeID("layerID"), params.layerSpec);
}

if (params.hasOwnProperty("compId")) {
    actionDescriptor.putInteger(stringIDToTypeID("compID"), params.compId);
} else if (params.hasOwnProperty("compIndex")) {
    actionDescriptor.putInteger(stringIDToTypeID("compIndex"), params.compIndex);
}

if (!params.includeAncestorMasks) {
    actionDescriptor.putEnumerated(
        stringIDToTypeID("includeAncestors"),
        stringIDToTypeID("includeLayers"),
        stringIDToTypeID("includeNone")
    );
} else {
    actionDescriptor.putEnumerated(
        stringIDToTypeID("includeAncestors"),
        stringIDToTypeID("includeLayers"),
        stringIDToTypeID("includeVisible")
    );
}

actionDescriptor.putEnumerated(
    stringIDToTypeID("includeAdjustors"),
    stringIDToTypeID("includeLayers"),
    stringIDToTypeID("includeVisible")
);

if (params.hasOwnProperty("convertToWorkingRGBProfile")) {
    actionDescriptor.putBoolean(stringIDToTypeID("convertToWorkingRGBProfile"), !!params.convertToWorkingRGBProfile);
}

if (params.hasOwnProperty("useICCProfile")) {
    actionDescriptor.putString(stringIDToTypeID("useICCProfile"), String(params.useICCProfile));
}

if (params.hasOwnProperty("getICCProfileData")) {
    actionDescriptor.putBoolean(stringIDToTypeID("sendThumbnailProfile"), !!params.getICCProfileData);
}

// NOTE: on the PS side, allowDither and useColorSettingsDither default to "true" if they are
// not set at all. However, in Generator, the common case will be that we do NOT want to dither,
// regardless of the settings in PS. So, on the Generator side, we default to false (hence the !! on
// the params properties).
actionDescriptor.putBoolean(stringIDToTypeID("allowDither"), !!params.allowDither);
actionDescriptor.putBoolean(stringIDToTypeID("useColorSettingsDither"), !!params.useColorSettingsDither);

if (params.hasOwnProperty("clipToDocumentBounds")) {
    actionDescriptor.putBoolean(stringIDToTypeID("clipToDocumentBounds"), !!params.clipToDocumentBounds);
}

if (params.clipBounds && params.usePSClipping) {

    // The part of the document to use
    var clipBounds = params.clipBounds,
        psClipRect = new ActionDescriptor();

    psClipRect.putUnitDouble(stringIDToTypeID("left"), charIDToTypeID("#Pxl"), clipBounds.left);
    psClipRect.putUnitDouble(stringIDToTypeID("top"), charIDToTypeID("#Pxl"), clipBounds.top);
    
    psClipRect.putUnitDouble(stringIDToTypeID("right"), charIDToTypeID("#Pxl"), clipBounds.right);
    psClipRect.putUnitDouble(stringIDToTypeID("bottom"), charIDToTypeID("#Pxl"), clipBounds.bottom);

    actionDescriptor.putObject(stringIDToTypeID("clipBounds"), stringIDToTypeID("clipBounds"), psClipRect);
}

if (params.boundsOnly) {
    actionDescriptor.putBoolean(stringIDToTypeID("boundsOnly"), params.boundsOnly);
}
actionDescriptor.putBoolean(stringIDToTypeID("bounds"), params.bounds);
//needs to be set explicitly as a boolean
if (params.thread === true || params.thread === false) {
    actionDescriptor.putBoolean(stringIDToTypeID("thread"), params.thread);
}

executeAction(stringIDToTypeID("sendLayerThumbnailToNetworkClient"), actionDescriptor, DialogModes.NO);
