/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4 */
/*global define: true, graphite: true, Image: true, setTimeout: true, mixpanel: true, FileReader: true, Backbone: true, XMLHttpRequest: true, window: true, Blob: true */

define(["jquery", "underscore", "mpAjax", "dwconfig", 'ccweb.files.extract/models/LayerModelMap', "ccweb.files.extract/utils/ImageUtil"], function ($, _, mpAjax, dwconfig, LayerModelMap, ImageUtil) {
    "use strict";
        
    var accessToken = "";
    var authorizationHeader = "";
    var authenticationHeader = {};
        
    var hosts = {
        STORAGE_SERVICE: 0,
        EXTRACT_SERVICE: 1
    };
    
    var urls = {
            WAIT_FOR_GRAPHITE_WORKER:       '/api/v1/worker/%s',
            // GET_ASSET_COLLECTION:           '/api/v1/psd',
            GRAPHITE_WORKER_VERSION:        '/api/v1/admin/graphiteVersion',
            INTROSPECT_PSD:                 '/api/v1/psd/%s/graphitejson',
            DELETE_ASSET:                   '/api/v1/psd/%s/delete',
            GET_DERIVED_ASSET_COLLECTION:   '/api/v1/psd/%s/derived',
            CREATE_DERIVED_ASSET:           '/api/v1/psd/%s/derived?nocache=%s',
            GET_WORKER_STATUS:              '/api/v1/worker/%s',
            GET_STORAGE_QUOTA:              '/api/v1/user/quota',
            // ASSET_THUMBNAIL_URL:            '/api/v1/asset/%s/thumbnail',
            ASSET_THUMBNAIL_URL:            '/%s/:rendition;size=200',
            SPRITESHEET_URL:                '/api/v1/psd/%s/spriteSheet/%s',
            TRACK_EVENT:                    '/api/v1/metrics/trackEvent',
            ASSET_INFO:                     '/api/v1/psd/%s/info',
            SET_WORKER_ON_ASSET:            '/api/v1/psd/%s/metadata',
            LAYER_URL:                      '/api/v1/psd/%s/layer/%s',
            DEFAULT_RENDITION_URL:          '/api/v1/psd/%s',
            DERIVED_RENDITION_URL:          '/api/v1/asset/%s/thumbnail',
            DERIVED_DOWNLOAD_URL:           '/api/v1/asset/%s/download',
            ASSET_ETAG:                     '/api/v1/asset/%s/etag',
            DELETE_DERIVED_ASSET:           '/api/v1/psd/%s/derived/delete',
            DERIVED_SPRITE_IMAGE:           '/api/v1/psd/%s/sprite/derived',
            USER_DATA:                      '/api/v1/user/data',
            THROTTLE_CHECK:                 '/api/v1/utility/throttlecheck',
            ANALYTICS_OPT_IN:               '/api/v1/user/analyticsOptIn'
        };
    
    var FILE_EXT_TO_CANVAS_MAP = {
        "png": "png",
        "png32": "png",
        "png8": "png",
        "jpg": "jpeg",
        "jpeg": "jpeg"
    };
    
    function getIndicesOf(searchStr, str, caseSensitive) {
        var startIndex = 0, searchStrLen = searchStr.length;
        var index, indices = [];
        if (!caseSensitive) {
            str = str.toLowerCase();
            searchStr = searchStr.toLowerCase();
        }
        while ((index = str.indexOf(searchStr, startIndex)) > -1) {
            indices.push(index);
            startIndex = index + searchStrLen;
        }
        return indices;
    }

    function sprintf() {
        // Basic implementation of sprintf to work for only %s. Will not handle any other format specifier - not even %%.
        var formatString = arguments[0],
            formattersLocation = getIndicesOf("%s", formatString, false),
            formatLength = formattersLocation.length;
        if (formatLength !== arguments.length - 1) {
            throw new SyntaxError("[sprintf] format specifers does not match with parameters passed ");
        }
        while (formatLength > 0) {
            formatString = formatString.replace("%s", arguments[formattersLocation.length - formatLength + 1].toString());
            formatLength = formatLength - 1;
        }
        return formatString;
    }
    
    function setFetchCallbacks(fetchOptions, successCallback, errorCallback, context) {
        if (successCallback) {
            fetchOptions.success = function (model, response) {
                successCallback.apply(context, [response]);
            };
        }

        if (errorCallback) {
            fetchOptions.error = function (model, response) {
                errorCallback.apply(context, [response]);
            };
        }

        return fetchOptions;
    }

    function setAJAXCallbacks(options, successCallback, errorCallback, context) {
        if (successCallback) {
            options.success = function (data, textStatus, response) {
                successCallback.apply(context, [response]);
            };
        }

        if (errorCallback) {
            options.error = function (response) {
                errorCallback.apply(context, [response]);
            };
        }
    }

    function constructProxyURL(url, serviceHost) {
        if (serviceHost === hosts.EXTRACT_SERVICE) {
            serviceHost = graphite.environment.EXTRACT_SERVICE.HOST;
        } else {
            serviceHost = graphite.environment.STORAGE_SERVICE.HOST;
        }
            
        return serviceHost + url;
    }
    
    var serverAPI = {
        setAccessToken: function (inAccessToken) {
            accessToken = inAccessToken;
            authorizationHeader = 'Bearer ' + accessToken;
            authenticationHeader = { 'Authorization' : authorizationHeader, 'X-api-key': 'DwExtract1', 'Accept': '*/*'};
        },
        
        createAjaxOptions: function (type, location) {
            var options = {
                'type': type,
                'url': graphite.environment.STORAGE_SERVICE.HOST + location,
                'headers': {
                    'Authorization': authorizationHeader,
                    'Accept': '*/*',
                    'X-api-key': 'DwExtract1'
                }
            };
            return options;
        },
        
        isPreviewLoading: function () {
            if ($('.preview-loading-spinner').is(':visible')) {
                return true;
            }
            return false;
        },
        
        // Waits for worker.
        waitForGraphiteWorker: function (workerGuid, callback) {
            var url = sprintf(urls.WAIT_FOR_GRAPHITE_WORKER, workerGuid),
                options = this.createAjaxOptions('GET', url, true),
                self = this;

            $.ajax(options).done(function (data, textStatus, jqXHR) {
                if (jqXHR.status === 200) {
                    var result = JSON.parse(data);
                    if (result.status === 'failed') {
                        graphite.events.trigger("hideWorkerProgress", workerGuid);
                        callback(data);
                    } else if (result.status === 'done') {
                        graphite.events.trigger("updateWorkerProgress", workerGuid, "done", 100);
                        callback(null, result.results, jqXHR);
                    } else {
                        // if we aren't finished wait a second and try again
                        graphite.events.trigger("updateWorkerProgress", workerGuid, result.status, result.progress);
                        setTimeout(function () { self.waitForGraphiteWorker(workerGuid, callback); }, 500);
                    }
                } else {
                    callback(data);
                }
            });
        },

        // APIs used by AssetController
        // Load an AssetCollection object, shouldn't need this for ccweb but added the API for AssetController
        loadAssetCollection: function (assetCollection, successCallback, errorCallback, context, inPath, xChildrenNextStart) {
            var path = inPath || '';
            path = encodeURI(path);
            var fetchOptions = this.createAjaxOptions('GET', dwconfig.STORAGE_SERVICE_URLS.STORAGE_ROOT + path);
            setFetchCallbacks(fetchOptions, successCallback, errorCallback, context);
            
            fetchOptions.cache = false;
            
            if (xChildrenNextStart) {
                fetchOptions.url = fetchOptions.url + '?start=' + xChildrenNextStart;
                fetchOptions.remove = false; //Append the newly fetched data to collection
            }
            
            var jqXHR = assetCollection.fetch(fetchOptions);
            jqXHR.done(function () {
                var xChildrenNextStart = jqXHR.getResponseHeader('X-Children-Next-Start');
                if (xChildrenNextStart) {
                    var incrementalFetchObj = {};
                    incrementalFetchObj.path = inPath;
                    incrementalFetchObj.xChildrenNextStart = xChildrenNextStart;
                    window.graphite.events.trigger('loadMoreAssets', incrementalFetchObj);
                } else {
                    window.graphite.events.trigger('loadingAssetsComplete');
                }
            });
        },
        
        /* Image Service utility to fetch Introspect and SpriteSheet Data. */
        imageServiceExecIntrospectImpl: function (sPsdPath, psdModel, callback) {
            var post_data, apiArgs = {}, i = 0,
                sResource = dwconfig.EXTRACT_SERVICE_URLS.INTROSPECT_PSD,
                options = {},
                aOutputs = ['output'],
                self = this;
        
            apiArgs.inputs = {};
            apiArgs.inputs.image = {};
            apiArgs.inputs.image.href = encodeURIComponent("/" + sPsdPath);
            
            if (null !== aOutputs) {
                apiArgs.outputs = {};
              
                for (i = 0; i < aOutputs.length; i++) {
                    apiArgs.outputs[aOutputs[i]] = {};
                    /* If disposition is set to 'location' we will need to make another request to download the output data.
                     * In our case we will receive multipart response and parse the same. 
                    */
                    //apiArgs.outputs[aOutputs[i]].disposition = 'location';
                    apiArgs.outputs[aOutputs[i]].accept = 'application/vnd.adobe.image-operation+json';
                }
            }
            
            var layerCompId = psdModel.get("layerCompId") || "";
            if (layerCompId !== '') {
                options.layerCompId = layerCompId.toString();
            }
            
            apiArgs.options = options;
            apiArgs.options.rendition = true;
            apiArgs.options.jsonVersion = "2.*";
            
            post_data = JSON.stringify(apiArgs);

            var ajaxOptions = { headers: { 'Authorization' : authorizationHeader, "x-api-key": 'DwExtract1', 'Content-Type': 'application/vnd.adobe.image-operation+json', 'x-request-id': window.dwPSDExtractManager.generateDwExtractClientRequestId()},
                url: graphite.environment.EXTRACT_SERVICE.HOST + sResource + "?api_key=DwExtract1",
                type: 'POST',
                data: post_data,
                port: graphite.environment.EXTRACT_SERVICE.PORT,
                'application/vnd.adobe.image-operation+json': function (contentId) {
                    /* Result corresponding to content type 'application/vnd.adobe.image-operation+json' in our multipart response. */
                },
                               
                'application/json': function (contentId) {
                    /* Result corresponding to content type 'application/json' in our multipart response. */
                    var data = self.packStringFromUint8Array(this),
                        parsedData = $.parseJSON(data);
                    
                    if (parsedData.imgdata) {
                        parsedData.imgdata.originalFile = sPsdPath.split('/').pop();
                        if (options.layerCompId) {
                            parsedData.imgdata.appliedLayerCompId = parseInt(options.layerCompId, 10);
                        }
                    }
                    
                    callback(true, parsedData, data);
                },

                'onError': function () {
                    window.graphite.serverAPI.clearServerLoadInfoTimer();
                    var fileName =  window.dwPSDExtractManager.extractFileNameFromPath(sPsdPath.replace(/\/+$/, ""));
                    window.graphite.events.trigger("fetch-extract-data-error", 'fetchExtractDataError', fileName);
                    $('.preview-loading-spinner').remove();
                }
                };
            /* Make AJAX request and parse the multipart response */
            $.multipart(ajaxOptions);
            
        },
        
        packStringFromUint8Array: function (arraybuffer) {
            var str = '',
                i = 0;
            for (i = 0; i < arraybuffer.byteLength; i++) {
                str += String.fromCharCode(arraybuffer[i]);
            }
            return str;
        },
        
        _uint8ArrayBufferToBase64: function (arrayBuffer, type) {
            var blob = new Blob([arrayBuffer], {'type': type}),
                reader = new FileReader(),
                deferred = new $.Deferred();

            reader.addEventListener("loadend", function () {
                deferred.resolve(reader.result);
            });
            reader.addEventListener("error", function () {
                deferred.reject();
            });
            
            reader.readAsDataURL(blob);
            return deferred.promise();
        },
       
        /* Image Service utility to fetch Introspect and SpriteSheet Data. */
        imageServiceExecSpritesheetImpl: function (sPsdPath, psdModel, callback) {
            var post_data, apiArgs = {}, i = 0, pngLocation = {},
                sResource = dwconfig.EXTRACT_SERVICE_URLS.SPRITESHEET_PSD, self = this,
                sheetJSON = '',
                jsonVersion = window.graphite.spritesheetJsonVersion;
        
            apiArgs.inputs = {};
            apiArgs.inputs.image = {};
            apiArgs.inputs.image.href = encodeURIComponent("/" + sPsdPath);
            
            apiArgs.outputs = {};
            apiArgs.outputs.spriteInfo = {};
            apiArgs.outputs.spriteInfo.accept = 'application/vnd.adobe.image-operation+json';
            
            apiArgs.outputs.sheets = {};
            //apiArgs.outputs.sheets.disposition = 'location'; //ENABLE it in case you want the SharedCloud path of Spritesheets and download separately.
            //apiArgs.outputs.sheets.accept = 'image/png'; //Omission of accept paramter defaults to PNG-32
            
            apiArgs.options = {};
            apiArgs.options.includeDrawable = true;
            apiArgs.options.minSheets = 6;
            apiArgs.options.clipLayers = true;
            apiArgs.options.flattenLayers = true;
            apiArgs.options.rendition = true;
            
            var layerCompId = psdModel.get("layerCompId") || "";
            if (layerCompId !== '') {
                apiArgs.options.layerCompId = layerCompId.toString();
            }
            
            post_data = JSON.stringify(apiArgs);
            
            var findKey = function (obj, value) {
                var key;
            
                _.each(_.keys(obj), function (k) {
                    var v = obj[k];
                    if (v.href === value) {
                        key = k;
                    }
                });
                return key;
            }, getSheetCount = function () {
                var count = 0;
                _.each(_.keys(sheetJSON.outputs), function (key) {
                    var matched = key.toLowerCase().match('sheet[0-9]*');
                    if (matched && matched.length > 0) {
                        count += 1;
                    }
                });
                return count;
            };
            
            var counter = 0,
                spritesheetJSON = {},
                spritesheetData = '',
                queuedImages = 0,
                spriteInfoCompleted = false,
                done = false;
            
            var ajaxOptions = { headers: { 'Authorization' : authorizationHeader, "x-api-key": 'DwExtract1', 'Content-Type': 'application/vnd.adobe.image-operation+json', 'x-request-id': window.dwPSDExtractManager.generateDwExtractClientRequestId()},
                url: graphite.environment.EXTRACT_SERVICE.HOST + sResource + "?api_key=DwExtract1",
                type: 'POST',
                data: post_data,
                port: graphite.environment.EXTRACT_SERVICE.PORT,
                'application/vnd.adobe.image-operation+json': function (contentId) {
                    /* Result corresponding to content type 'application/vnd.adobe.image-operation+json' in our multipart response. */
                    var data = self.packStringFromUint8Array(this),
                        i = 0;
                    sheetJSON = $.parseJSON(data);
                    
                    if (apiArgs.outputs.sheets.disposition === 'location') {
                        for (i = 0; i < apiArgs.options.minSheets; i++) {
                            var sheet = 'sheet' + (i + 1);
                            if (sheetJSON.outputs && sheetJSON.outputs.hasOwnProperty(sheet) && sheetJSON.outputs[sheet].location) {
                                pngLocation[sheet] = sheetJSON.outputs[sheet].location;
                            }
                        }
                    } else {
                        queuedImages = getSheetCount();
                    }
                },
                               
                'application/json': function (contentId) {
                    spritesheetData = self.packStringFromUint8Array(this);
                    /* Result corresponding to content type 'application/json' in our multipart response. */
                    spritesheetJSON = $.parseJSON(spritesheetData);
                    if (apiArgs.outputs.sheets.disposition === 'location') {
                        spritesheetJSON.pngLocation = pngLocation;
                    }
                },
                               
                'image/png': function (contentId) {
                    var data = this;
                    var cid = 'cid:' + contentId.substring(1, contentId.length - 1);
                    var sheetID = findKey(sheetJSON.outputs, cid);
                    
                    var pathSeparator  = '/',
                        path = "files",
                        md5 = psdModel.get("md5"),
                        layerCompId = psdModel.get("layerCompId");
                    
                    jsonVersion = jsonVersion.replace(/\./g, "_"); //Replace all the '.'s with '_'
            
                    path = window.dwData.configPath + path + pathSeparator;
                    path +=  md5 + "/spritesheets/v_" + jsonVersion + pathSeparator + sheetID + "_" + layerCompId + ".png";
                    
                    var pathExistsCallback = function (pathExists) {
                        if (pathExists) {
                            queuedImages -= 1;
                            /* In case of all the queued spritesheets are copied locally and
                             * Parsing has already finished, call the callback immediately with the spritesheet JSON. 
                             */
                            
                            if (queuedImages === 0 && spriteInfoCompleted === true) {
                                callback(true, spritesheetJSON, spritesheetData);
                            }
                        } else {
                            
                            var result = '',
                                type = 'image/png',
                                conversionToBase64Fail = function () {
                                    window.graphite.serverAPI.errorInExtractData(sPsdPath);
                                },
                                conversionToBase64Complete = function (base64Data) {
                                    var writeBase64Callback = function (writeSucceeded) {
                                        if (writeSucceeded) {
                                            queuedImages -= 1;
                                            if (queuedImages === 0 && spriteInfoCompleted === true) {
                                                callback(true, spritesheetJSON, spritesheetData);
                                            }
                                        } // else {  // we should show some form of error here as the PSD will not get rendered }
                                    };

                                    window.dwPSDExtractManager.writeBase64(path, base64Data, accessToken, writeBase64Callback);
                                },
                                conversionPromise = self._uint8ArrayBufferToBase64(data, type);
                            conversionPromise.done(conversionToBase64Complete);
                            conversionPromise.fail(conversionToBase64Fail);
                        }
                    };
                    
                    window.dwPSDExtractManager.pathExists(path, pathExistsCallback);
                },
                               
                'onSuccess': function () {
                    spriteInfoCompleted = true;
                    
                    /* In case of all the queued spritesheets are copied locally and
                     * Parsing has finished, call the callback immediately with the spritesheet JSON. */
                    if ((apiArgs.outputs.sheets.disposition === 'location') || (queuedImages === 0)) {
                        callback(true, spritesheetJSON, spritesheetData);
                    }
                },
                
                'onError': function () {
                    window.graphite.serverAPI.errorInExtractData(sPsdPath);
                }
                               
                };
            
            /* Make AJAX request and parse the multipart response */
            $.multipart(ajaxOptions);
            
        },
        
        errorInExtractData: function (sPsdPath) {
            this.clearServerLoadInfoTimer();
            var fileName =  window.dwPSDExtractManager.extractFileNameFromPath(sPsdPath.replace(/\/+$/, ""));
            window.graphite.events.trigger("fetch-extract-data-error", 'fetchExtractDataError', fileName);
            $('.preview-loading-spinner').remove();
        },
        
        getCachedJSONPath: function (psdModel, jsonType) {
            var jsonVersion   = '',
                pathSeparator = '/',
                path = "files",
                md5 = psdModel.get("md5"),
                layerCompId = psdModel.get("layerCompId");
            
            if (jsonType === 'introspect') {
                jsonVersion = window.graphite.introspectJsonVersion;
            } else {
                jsonVersion = window.graphite.spritesheetJsonVersion;
            }
            
            jsonVersion = jsonVersion.replace(/\./g, "_"); //Replace all the '.'s with '_'
            
            path = window.dwData ? window.dwData.configPath + path + pathSeparator : "";
            path +=  md5 + "/json/" + jsonType + "/v_" + jsonVersion + "/" + jsonType + "_" + layerCompId + ".json";
            return path;
        },
        
        cacheGraphiteJSON: function (psdModel, jsonRawData, jsonType) {
            var path = this.getCachedJSONPath(psdModel, jsonType);
            
            var pathExistsCallback = function (pathExists) {
                if (pathExists) {
                    return;
                } else {
                    window.dwPSDExtractManager.writeString(path, jsonRawData, null);
                }
            };
            window.dwPSDExtractManager.pathExists(path, pathExistsCallback);
        },
        
        getJSONFileContent: function (path, successCallback, errorCallback) {
            $.ajax({
                url:        path,
                async:      true,
                cache:		false,
                contentType: 'application/json',
                dataType:   'json',
                success:    function (fileData, status) {
                    if (successCallback) {
                        successCallback(fileData);
                    }
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    if (errorCallback) {
                        errorCallback(xhr);
                    }
                }
            });
        },
        
        clearServerLoadInfoTimer: function () {
            clearTimeout(this.serverLoadInfoTimerID);
            graphite.events.off('route-change', this.clearServerLoadInfoTimer, this);
        },
        
        fetchIntrospectAndSpriteDataFromServer: function (inPath, psdModel, callback) {
            var introData = null,
                spriteSheetData = null,
                self = this,
                timerDuration =  60 * 1000;
                
            this.serverLoadInfoTimerID = setTimeout(function () {
                if (navigator.onLine) {
                    graphite.events.trigger("server-taking-too-long");
                }
            }, timerDuration);
                
            graphite.events.on('route-change', this.clearServerLoadInfoTimer, this);
            this.imageServiceExecIntrospectImpl(inPath, psdModel, function (status, jsonData, rawData) {
                if (status) {
                    introData = jsonData;
                    introData.dataType = 'JSONOnly';
                    self.cacheGraphiteJSON(psdModel, rawData, 'introspect');
                    psdModel.parse(introData);
                    if (spriteSheetData) {
                        self.clearServerLoadInfoTimer();
                        callback(true);
                    }
                }
            });
            
            this.imageServiceExecSpritesheetImpl(inPath, psdModel, function (status, jsonData, rawData) {
                if (status) {
                    spriteSheetData = jsonData;
                    spriteSheetData.dataType = 'spriteSheetOnly';
                    self.cacheGraphiteJSON(psdModel, rawData, 'spritesheet');
                    psdModel.parse(spriteSheetData);
                    if (introData) {
                        self.clearServerLoadInfoTimer();
                        if (spriteSheetData.pngLocation) {
                            psdModel.set("pngLocation", spriteSheetData.pngLocation);
                        }
                        callback(true);
                    }
                }
            });
        },
        
        loadPsdModelFromPath: function (psdModel, inPath, successCallback, errorCallback, context) {
            if (psdModel.get("welcomeExp")) {
                this.getJSONFileContent("../WelcomeExp/cached_" + psdModel.get("layerCompId") + ".json", function (fileData) {
                    psdModel.parse(fileData);
                    successCallback.apply(context);
                }, function (xhr) {
                });
            } else {
                var fetchOptions = { url: inPath },
                    self = this,
                    cachedIntrospectJSONPath = this.getCachedJSONPath(psdModel, "introspect"),
                    cachedSpritesheetJSONPath = this.getCachedJSONPath(psdModel, "spritesheet"),
                    introspectData,
                    spritesheetData;

                this.fetchIntroAndSpriteCallback = function (status) {
                    if (status === true) {
                        successCallback.apply(context);
                    }
                };


                /* Let's fetch the Introspect and Spritesheet data from cache first.
                 * If data is not present in cache:
                 * Fetch the content from Image service and update the cache.
                 */
                var introPathExistsCallback = function (introPathExists) {
                    if (introPathExists) {
                        var spritesheetPathExistsCallback = function (spritesheetPathExists) {
                            if (spritesheetPathExists) {
                                var _successIntrospectCallback = function (data) {
                                    introspectData = data;
                                    introspectData.dataType = 'JSONOnly';
                                    var _successSpritesheetCallback = function (data) {
                                        /* Both Introspect and Spritesheet data has been retrieved successfully from Cache.
                                         * Parse the JSON and Merge them.
                                         * Pass the merged JSON to PSD model.
                                        */
                                        spritesheetData = data;
                                        spritesheetData.dataType = 'spriteSheetOnly';
                                        psdModel.parse(spritesheetData);
                                        psdModel.parse(introspectData);
                                        successCallback.apply(context);
                                    }, _errorSpritesheetCallback = function (xhr) {

                                        self.fetchIntrospectAndSpriteDataFromServer(inPath, psdModel, self.fetchIntroAndSpriteCallback);

                                    };

                                    /* Introspect data has been successfully retrieved from cache. 
                                     * Try to fetch the Spritesheet adta as well from cache. 
                                     */
                                    self.getJSONFileContent(cachedSpritesheetJSONPath, _successSpritesheetCallback,  _errorSpritesheetCallback);

                                }, _errorIntrospectCallback = function (xhr) {

                                    self.fetchIntrospectAndSpriteDataFromServer(inPath, psdModel, self.fetchIntroAndSpriteCallback);

                                };

                                /* If you reached here then:
                                 * Both the introspect and spritesheet data is in cache. 
                                 * No need to make a call to Image Serivce. 
                                 */
                                self.getJSONFileContent(cachedIntrospectJSONPath, _successIntrospectCallback,  _errorIntrospectCallback);

                            } else {

                                /* Cached Spritesheet data is not found. Make a call to Image Service to fetch the content.  */
                                self.fetchIntrospectAndSpriteDataFromServer(inPath, psdModel, self.fetchIntroAndSpriteCallback);

                            }
                        };

                        window.dwPSDExtractManager.pathExists(cachedSpritesheetJSONPath, spritesheetPathExistsCallback);

                    } else {
                        /* Cached Introspect data is not found. Make a call to Image Service to fetch the content.  */
                        self.fetchIntrospectAndSpriteDataFromServer(inPath, psdModel, self.fetchIntroAndSpriteCallback);
                    }
                };
                window.dwPSDExtractManager.pathExists(cachedIntrospectJSONPath, introPathExistsCallback);
            }
        },
        
        customFilter: function (object, layerId, value, property) {
            var self = this,
                i = 0;
            if (object.hasOwnProperty('layerId') && object.layerId === layerId) {
                if (property === 'spriteSheet') {
                    object.spriteSheet = value;
                } else if (property === 'flattenedSprite') {
                    object.flattenedSprite = value;
                }
                return;
            }
            
            for (i = 0; i < Object.keys(object).length; i++) {
                if (object && typeof object[Object.keys(object)[i]] === "object") {
                    self.customFilter(object[Object.keys(object)[i]], layerId, value, property);
                }
            }
            return;
        },
       
        mergeIntroAndLayerSpriteSheets: function (introData, spriteData) {
            var index = 0;
            for (index = 0; index < spriteData.layers.length; index++) {
                var layer = spriteData.layers[index];
                if (layer.spriteSheet) {
                    this.customFilter(introData, layer.layerId, layer.spriteSheet, 'spriteSheet');
                }
                if (layer.flattenedSprite) {
                    this.customFilter(introData, layer.layerId, layer.flattenedSprite, 'flattenedSprite');
                }
            }
            return introData;
        },

        isGraphiteVersionGreater : function (graphiteJSONVersion, psdJSONVersion) {
            var lhsParts = graphiteJSONVersion.split(".");
            var rhsParts = psdJSONVersion.split(".");

            lhsParts[0] = parseInt(lhsParts[0], 10);
            lhsParts[1] = parseInt(lhsParts[1], 10);
            lhsParts[2] = parseInt(lhsParts[2], 10);

            rhsParts[0] = parseInt(rhsParts[0], 10);
            rhsParts[1] = parseInt(rhsParts[1], 10);
            rhsParts[2] = parseInt(rhsParts[2], 10);

            if ((lhsParts[0] > rhsParts[0]) ||
                    (lhsParts[0] === rhsParts[0] && lhsParts[1] > rhsParts[1]) ||
                    (lhsParts[0] === rhsParts[0] && lhsParts[1] === rhsParts[1] && lhsParts[2] > rhsParts[2])) {
                return true;
            }
            return false;
        },
        
        downloadWebAsset: function (ccStorageUrl, callback, savePath) {
            var self = this,
                standardHeader = {},
                pathStr = "";
            
            var indexOfPath = window.graphite.environment ? ccStorageUrl.indexOf(window.graphite.environment.STORAGE_SERVICE.HOST) : -1;
            var getImageData = function () {
                var oReq = new XMLHttpRequest();
                oReq.open("get", ccStorageUrl, true);
                oReq.responseType = "blob";
                oReq.setRequestHeader("Authorization", standardHeader.Authorization);
                oReq.setRequestHeader("x-api-key", 'DwExtract1');
                oReq.setRequestHeader("Accept", 'image/png, image/jpeg, image/jpg, application/pdf, text/html, text/plain, */*;q=0.1');
                oReq.onload = function (oEvent) {
                    if (oReq.status === 200) {
                        var blob = oReq.response,
                            reader = new FileReader();

                        reader.addEventListener("loadend", function () {
                            if (callback) {

                                var writeBase64Calbback = function (writeSucceeded) {
                                    callback.apply(null, [reader.result]);
                                };

                                window.dwPSDExtractManager.writeBase64(pathStr, reader.result, accessToken, writeBase64Calbback);
                            }
                        });
                        reader.readAsDataURL(blob);
                    }
                };
                oReq.onerror = function (data) {
                    // TBI
                };
                oReq.send(null);
            };
            
			standardHeader.Authorization = 'Bearer ' + accessToken;

            if (indexOfPath >= 0) {
                indexOfPath = indexOfPath + graphite.environment.STORAGE_SERVICE.HOST.length; // Length of api/v1/;
                if (savePath) {
                    pathStr = savePath;
                } else {
                    pathStr = ccStorageUrl.substring(indexOfPath);
                }
            }
            if (pathStr && window.dwData) {
                
                if (!savePath) {
                    if (pathStr.charAt(0) === '/') {
                        pathStr.slice(1);
                    }
                    
                    pathStr = window.dwData.configPath + pathStr;
                }
                
                var pathExistsCallback = function (pathExists) {
                    if (pathExists) {
                        if (callback) {
                            callback.apply(null, [pathStr]);
                        }
                        return;
                    } else {
                        getImageData();
                    }
                };
                window.dwPSDExtractManager.pathExists(pathStr, pathExistsCallback);
            } else {
                getImageData();
            }
        },
        
        getDerivedAssetFromLayerIds: function (layerIds, imageFormat, psdModel, filePath, imageDownloadOptions, errorHandlers) {
            var iImageQuality = 1.0, // TO_DO: This needs to be read from the preference.
                deferred = new $.Deferred(),
                post_data,
                apiArgs = {},
                successFunc = function (imgData) {
					if (imgData === true) {
                        deferred.resolve(imgData);
                    } else {
						deferred.reject(imgData);
                    }
                },
                isBatchExport = (imageDownloadOptions && imageDownloadOptions.isBatchExport === true),
                assetsToFilepath = {},
                outputAccept;
            
            if (imageDownloadOptions && imageDownloadOptions.imageQuality) {
                iImageQuality = imageDownloadOptions.imageQuality / 100;
            }
            
            //right now we have only one output type for batch extract also
            if (imageFormat === "svg") {
                outputAccept = 'image/svg+xml';
            } else if (imageFormat === "jpeg" || imageFormat === "jpg") {
                outputAccept = 'image/jpeg; q=' + iImageQuality;
            } else if (imageFormat === "png-32") {
                outputAccept = 'image/png'; //Special case: png-32 doesn't require x-format parameter.
            } else {
                outputAccept = 'image/png; x-format=' + imageFormat;
            }
            
            apiArgs.inputs = {};
            apiArgs.inputs.image = {};
            apiArgs.inputs.image.href = encodeURIComponent("/" + psdModel.get("path"));
              
            // --- outputs ---
            apiArgs.outputs = {};
            
            // --- options ---
            apiArgs.options = {};
            
            if (!isBatchExport) {
                // -- Single file download. --
                apiArgs.outputs.output = {};
                // apiArgs.outputs.output.disposition = 'location';
                
                //output format
                apiArgs.outputs.output.accept = outputAccept;
                
                //scale
                if (imageDownloadOptions && imageDownloadOptions.scaleFactor) {
                    apiArgs.options.scale = imageDownloadOptions.scaleFactor;
                }
                
                //layers
                apiArgs.options.layerIds = layerIds;
            } else {
                //Batch download
                if (imageDownloadOptions && imageDownloadOptions.deviceTypesPaths) {
                    var i, assetKey, deviceTypes = imageDownloadOptions.deviceTypesPaths;
                    for (i = 1; i <= deviceTypes.length; ++i) {
                        //create the options for each asset.
                        //should be in the format
                        //outputs: {
                        //          asset1: {accept:"image/png", options: {layerIds:[], scale:1.0}},
                        //          asset2: {accept:"image/png", options: {layerIds:[], scale:2.0}},
                        //          ...
                        //          assetN: {accept:"image/png", options: {layerIds:[], scale:1.0}}
                        //         };
                        
                        assetKey = "asset" + i;
                        apiArgs.outputs[assetKey] = {
                            accept : outputAccept,
                            options : {
                                layerIds : layerIds,
                                scale : deviceTypes[i - 1].scale
                            }
                        };
                        //update our map of assetId to filePaths
                        assetsToFilepath[assetKey] = deviceTypes[i - 1].filepath;
                    }
                }
            }
            
            post_data = JSON.stringify(apiArgs);

            if (!isBatchExport) {
                this.createWebAsset(constructProxyURL(dwconfig.EXTRACT_SERVICE_URLS.CREATE_WEB_ASSET, hosts.EXTRACT_SERVICE), post_data, successFunc, filePath, errorHandlers);
            } else {
                this.batchDownloadAsset(constructProxyURL(dwconfig.EXTRACT_SERVICE_URLS.BATCH_DOWNLOAD_ASSET, hosts.EXTRACT_SERVICE), post_data, successFunc, assetsToFilepath, errorHandlers);
            }
            
            return deferred;
        },
        
        getDerivedAsset : function (layerModels, imageFormat, psdModel, filePath, imageDownloadOptions, errorHandlers) {
            var layerIds = [],
                i = 0;
            
            for (i = 0; i < layerModels.length; ++i) {
                layerIds.push(layerModels[i].get("layerId"));
            }
            
            var isProcessed = false;
            
            if ((!imageDownloadOptions || !imageDownloadOptions.isBatchExport) && !window.graphite.routers.main.isWelcomeExpDismissed()) {
                var deferred = new $.Deferred();
                
                if (layerModels.length > 0) {
                
                    // Only pick up the first model.
                    var layerModel = layerModels[0];

                    var elem = LayerModelMap.getLayerInfoForId(layerModel.get("layerId")).item;
                    var canvasRef = elem[0];

                    if (canvasRef) {
                        var imageQuality = 1.0;
                        
                        if (imageDownloadOptions && imageDownloadOptions.imageQuality) {
                            imageQuality = imageDownloadOptions.imageQuality / 100;
                        }
                        
                        var result = ImageUtil.saveCanvasAsImage(canvasRef, FILE_EXT_TO_CANVAS_MAP[imageFormat], imageQuality, "#FFFFFF");
                        
                        var writeBase64Calbback = function (writeSucceeded) {

                            if (writeSucceeded) {
                                deferred.resolve(null);
                            } else {
                                deferred.reject('error');
                            }
                        };

                        window.dwPSDExtractManager.writeBase64(filePath, result, accessToken, writeBase64Calbback);
                        return deferred;

                    } else {
                        return deferred.reject('error').promise();
                    }
                } else {
                    return deferred.reject('error').promise();
                }
            }
            
            if (!isProcessed) {
                return this.getDerivedAssetFromLayerIds(layerIds, imageFormat, psdModel, filePath, imageDownloadOptions, errorHandlers);
            }
            
        },
        
        createWebAsset: function (createAssetUrl, post_data, callback, savePath, errorHandlers) {
            var self = this,
                conversionToBase64Fail = function () {
                    errorHandlers.onerror();
                },
                conversionToBase64Complete = function (base64Data) {
                    window.dwPSDExtractManager.writeBase64(savePath, base64Data, accessToken, callback);
                };
            
            var options = { headers: { 'Authorization' : authorizationHeader, "x-api-key": 'DwExtract1', 'Content-Type': 'application/vnd.adobe.image-operation+json',  'x-request-id': window.dwPSDExtractManager.generateDwExtractClientRequestId()},
                url: createAssetUrl,
                type: 'POST',
                data: post_data,
                port: window.graphite.environment.EXTRACT_SERVICE.PORT,
                'application/vnd.adobe.image-operation+json': function () {
                    // Do Nothing.
                },
                'image/png': function (contentId) {
                    var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/png');
                    conversionPromise.done(conversionToBase64Complete);
                    conversionPromise.fail(conversionToBase64Fail);
                },
                'image/jpeg': function (contentId) {
                    var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/jpeg');
                    conversionPromise.done(conversionToBase64Complete);
                    conversionPromise.fail(conversionToBase64Fail);
                },
                'image/svg+xml': function (contentId) {
                    var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/svg+xml');
                    conversionPromise.done(conversionToBase64Complete);
                    conversionPromise.fail(conversionToBase64Fail);
                },

                'onError': function (responseStatus) {
                    errorHandlers.onerror(responseStatus);
                }
                };

            $.multipart(options);

        },
        
        batchDownloadAsset: function (batchDownloadUrl, post_data, callback, assetsToFilepath, errorHandlers) {
            var self = this,
                conversionToBase64Fail = function () {
                    errorHandlers.onerror();
                },
                contentIdToFilepaths = {},
                conversionToBase64Complete = function (base64Data, contentId) {
                    //get the filepath to write from the content Id
                    if (contentId.length > 0 && contentIdToFilepaths["cid:" + contentId]) {
                        var curFilepath = contentIdToFilepaths["cid:" + contentId];
                        if (curFilepath.length > 0) {
                            window.dwPSDExtractManager.writeBase64(curFilepath, base64Data, accessToken, callback);
                        }
                    }
                };
            
            var options = { headers: { 'Authorization' : authorizationHeader, "x-api-key": 'DwExtract1', 'Content-Type': 'application/vnd.adobe.image-operation+json',  'x-request-id': window.dwPSDExtractManager.generateDwExtractClientRequestId()},
                url: batchDownloadUrl,
                type: 'POST',
                data: post_data,
                port: window.graphite.environment.EXTRACT_SERVICE.PORT,
                'application/vnd.adobe.image-operation+json': function () {
                    //parse the JSON data to get the content Ids for each asset
                    var data = self.packStringFromUint8Array(this),
                        parsedData = $.parseJSON(data);
                    if (parsedData.outputs) {
                        var outputAssests = parsedData.outputs;
                        var assetID;
                        for (assetID in outputAssests) {
                            if (outputAssests.hasOwnProperty(assetID) && outputAssests[assetID].href) {
                                var curAssetId = outputAssests[assetID].href;
                                //update the contentId to filepath map
                                //href will be in teh form of 'cid:content-id-value'
                                contentIdToFilepaths[curAssetId] = assetsToFilepath[assetID];
                            }
                        }
                    }
                },
                'image/png': function (contentId) {
                    if (contentId && contentId.length > 0) {
                        var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/png');
                        conversionPromise.done(function (base64Data) {
                            //content id is in teh form of '<contend-id-value>'
                            //so remove first and last characters
                            var curContentId = contentId.substr(1, contentId.length - 2);
                            conversionToBase64Complete(base64Data, curContentId);
                        });
                        conversionPromise.fail(conversionToBase64Fail);
                    }
                },
                'image/jpeg': function (contentId) {
                    if (contentId && contentId.length > 0) {
                        var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/png');
                        conversionPromise.done(function (base64Data) {
                            //content id is in teh form of '<contend-id-value>'
                            //so remove first and last characters
                            var curContentId = contentId.substr(1, contentId.length - 2);
                            conversionToBase64Complete(base64Data, curContentId);
                        });
                        conversionPromise.fail(conversionToBase64Fail);
                    }
                },
                'image/svg+xml': function (contentId) {
                    if (contentId && contentId.length > 0) {
                        var conversionPromise = self._uint8ArrayBufferToBase64(this, 'image/png');
                        conversionPromise.done(function (base64Data) {
                            //content id is in teh form of '<contend-id-value>'
                            //so remove first and last characters
                            var curContentId = contentId.substr(1, contentId.length - 2);
                            conversionToBase64Complete(base64Data, curContentId);
                        });
                        conversionPromise.fail(conversionToBase64Fail);
                    }
                },
                'onError': function (responseStatus) {
                    errorHandlers.onerror(responseStatus);
                }
                };

            $.multipart(options);
        },

        // Delete an asset from the users collection, shouldn't need this for ccweb but added to the API for AssetController
        // removed from AssetController because with this API the controller method did nothing extra
        deleteAsset: function (assetId, successCallback, errorCallback, context) {
            var url = sprintf(urls.DELETE_ASSET, assetId),
                options = this.createAjaxOptions('DELETE', url);
            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },

        // APIs used by DerivedAssetController
        loadDerivedAssetCollection: function (derivedAssetCollection, psdModel, successCallback, errorCallback, context) {
            var url = sprintf(urls.GET_DERIVED_ASSET_COLLECTION, psdModel.get('id')),
                fetchOptions = this.createAjaxOptions('GET', url);
            fetchOptions.reset = true;
            setFetchCallbacks(fetchOptions, successCallback, errorCallback, context);
            derivedAssetCollection.fetch(fetchOptions);
        },

        createDerivedAsset: function (jsonPostObject, successCallback, errorCallback, context) {
            var jsonPostData = JSON.stringify(jsonPostObject),
                url = sprintf(urls.CREATE_DERIVED_ASSET, jsonPostObject.psdGuid, $.now()),
                fetchOptions = this.createAjaxOptions('POST', url),
                options = _.extend({
                    data: jsonPostData,
                    dataType: "json"
                }, fetchOptions);

            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },

        getWorkerStatus: function (workerGuid, successCallback, errorCallback, context) {
            var url = sprintf(urls.GET_WORKER_STATUS, workerGuid),
                options = this.createAjaxOptions('GET', url);

            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },
        
        getStorageQuota: function (successCallback, errorCallback, context) {
            var options = this.createAjaxOptions('GET', urls.GET_STORAGE_QUOTA);
            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },

        // API used by ThumbnailController - probably don't need for ccweb
        getAssetThumbnailUrl: function (assetID) {
            var location = Backbone.history.fragment || "files",
                url = sprintf(urls.ASSET_THUMBNAIL_URL, location + "/" + assetID);
            return constructProxyURL(url);
        },
        
        getAssetThumbnail: function (assetID, callback, context) {
            var url = sprintf(urls.ASSET_THUMBNAIL_URL, assetID),
                successCallback = function (result) {
                    callback.apply(context, [result]);
                },
                errorThrownCallback = function (result) {
                    callback.apply(context, ["", result]);
                };
            this.downloadImageData(url, successCallback, errorThrownCallback);
        },
        
        getAssetImageUrl: function (location) {
            location = location || '';
            return graphite.environment.STORAGE_SERVICE.HOST + location;
        },
        
        downloadImageData: function (url, successCallback, errorCallback, context) {
            var oReq = new XMLHttpRequest(),
                fetchOptions = this.createAjaxOptions('GET', url);
            oReq.open(fetchOptions.type, fetchOptions.url, true);
            oReq.responseType = 'blob';
            oReq.setRequestHeader("Authorization", fetchOptions.headers.authorization);
            oReq.setRequestHeader("Accept", "image/png");

            oReq.onload = function (oEvent) {
                if (oReq.status === 200) {
                    var blob = oReq.response,
                        reader = new FileReader();
    
                    reader.addEventListener("loadend", function () {
                        successCallback.apply(context, [reader.result]);
                    });
                    reader.readAsDataURL(blob);
                } else {
                    errorCallback.apply(context, [oReq.status, oReq.statusText]);
                }
            };

            oReq.send(null);
        },
        
        getSpriteSheet: function (psdGuid, layerCompId, spriteSheetId, callback, context) {
            var layerInfo = layerCompId ? "/" + layerCompId : "",
                url = sprintf(urls.SPRITESHEET_URL, (psdGuid + layerInfo).toString(), spriteSheetId),
                successCallback = function (result) {
                    callback.apply(context, [result]);
                },
                errorThrownCallback = function (result) {
                    callback.apply(context, ["", result]);
                };
            this.downloadImageData(url, successCallback, errorThrownCallback);
        },
        
        // API used by LayerModel
        getSpriteSheetURL: function (path) {
            var location = graphite.environment ? graphite.environment.STORAGE_SERVICE.HOST + path : "";
            return location;
        },

        // ___________ metrics_________
        // called from MetricsProxyModel

        registerPersistentParameters: function (params) {
        },

        trackEvent: function (params, successCallback, errorCallback, context) {
        },

        trackExtractEvent: function (params) {
            window.dwPSDExtractManager.logExtractEvent(params);
        },

        // ________end metrics_________

        // APIs used by AssetView - probably don't need for ccweb
        getAssetInfo: function (assetId, successCallback, errorCallback, context) {
            var url = sprintf(urls.ASSET_INFO, assetId),
                options = this.createAjaxOptions('GET', url);

            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },

        setWorkerOnAsset: function (workderId, assetId, successCallback, errorCallback, context) {
            var jsonPostObject = {"graphiteWorker": workderId},
                jsonPostData = JSON.stringify(jsonPostObject),
                url = sprintf(urls.SET_WORKER_ON_ASSET, assetId),
                fetchOptions = this.createAjaxOptions('PUT', url),
                options = _.extend({
                    data: jsonPostData,
                    contentType: "application/json; charset=utf-8",
                    dataType: "json"
                }, fetchOptions);

            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },

        // API used by InspectStyleOverlayView
        getLayerURL: function (psdGuid, layerId) {
            var url = sprintf(urls.LAYER_URL, psdGuid, layerId);
            return constructProxyURL(url);
        },

        getLayer: function (psdGuid, layerId, callback, context) {
            var url = sprintf(urls.LAYER_URL, psdGuid, layerId),
                successCallback = function (result) {
                    callback.apply(context, [result]);
                },
                errorThrownCallback = function (result) {
                    callback.apply(context, ["", result]);
                };
            this.downloadImageData(url, successCallback, errorThrownCallback);
        },

        getDefaultRenditionURL: function (psdGuid) {
            var url = sprintf(urls.DEFAULT_RENDITION_URL, psdGuid);
            return constructProxyURL(url);
        },

        getDefaultRendition: function (psdGuid, callback, context) {
            var url = sprintf(urls.DEFAULT_RENDITION_URL, psdGuid),
                successCallback = function (result) {
                    callback.apply(context, [result]);
                },
                errorThrownCallback = function (result) {
                    callback.apply(context, ["", result]);
                };
            this.downloadImageData(url, successCallback, errorThrownCallback);
        },
        
        getDerivedRenditionURL: function (assetID) {
            var url = sprintf(urls.DERIVED_RENDITION_URL, assetID);
            return constructProxyURL(url);
        },

        // APIs used by ViewTemplates.html

        getDerivedDownloadURL: function (assetID, name) {
            var url = sprintf(urls.DERIVED_DOWNLOAD_URL, assetID);
            return constructProxyURL(url) + '&download=true&filename=' + encodeURIComponent(name);
        },

        getAssetETag: function (assetId, successCallback, errorCallback, context) {
            var url = sprintf(urls.ASSET_ETAG, assetId),
                options = this.createAjaxOptions('GET', url);
            setAJAXCallbacks(options, successCallback, errorCallback, context);
            $.ajax(options);
        },
        
        deleteDerivedAsset: function (derivedAssetId, successCallback, errorCallback, context) {
            var url = sprintf(urls.DELETE_DERIVED_ASSET, derivedAssetId),
                options = this.createAjaxOptions('DELETE', url);
            setAJAXCallbacks(options, successCallback, errorCallback, context);

            $.ajax(options);
        },

        getDerivedSpriteImage: function (sPsdPath, layerCompId, layers, successCallback, errorCallback, context) {
            var image = new Image(),
                createAssetUrl = constructProxyURL(dwconfig.EXTRACT_SERVICE_URLS.CREATE_WEB_ASSET, hosts.EXTRACT_SERVICE);
                       
            var post_data, apiArgs = {},
                sResource = dwconfig.EXTRACT_SERVICE_URLS.CREATE_WEB_ASSET, self = this;

            image.onload = function () {
                successCallback.apply(context);
            };
            
            image.onerror = function (result) {
                errorCallback.apply(context, [result]);
            };

            apiArgs.inputs = {};
            apiArgs.inputs.image = {};
            apiArgs.inputs.image.href = "/" + sPsdPath;
              
            apiArgs.outputs = {};
            apiArgs.outputs.output = {};
            apiArgs.outputs.output.accept = 'image/png';
            
            apiArgs.options = {};
            apiArgs.options.layerIds = layers;
            if (layerCompId) {
                apiArgs.options.layerCompId = layerCompId;
            }
            
            post_data = JSON.stringify(apiArgs);
            
            var options = { headers: { 'Authorization' : authorizationHeader, "x-api-key": 'DwExtract1', 'Content-Type': 'application/vnd.adobe.image-operation+json'},
                url: createAssetUrl,
                type: 'POST',
                data: post_data,
                port: window.graphite.environment.EXTRACT_SERVICE.PORT,
                'image/png': function (contentId) {
                    var data = this,
                        conversionToBase64Complete = function (base64Data) {
                            image.src = base64Data;
                        },
                        conversionToBase64Fail = function () {
                            window.graphite.events.trigger("fetch-psd-layers-error", 'fetchPSDLayersError');
                            $(".spinner").hide();
                        },
                        conversionPromise = self._uint8ArrayBufferToBase64(data, 'image/png');
                    conversionPromise.done(conversionToBase64Complete);
                    conversionPromise.fail(conversionToBase64Fail);
                },

                'onError': function (responseStatus) {
                    window.graphite.events.trigger("fetch-psd-layers-error", 'fetchPSDLayersError');
                    $(".spinner").hide();
                }
                };

            $.multipart(options);
            return image;
        },

        applyCrossDomainPolicy: function (image) {
            // NOOP
        },

        getUserData: function (callback) {
            var options = this.createAjaxOptions('GET', urls.USER_DATA);
            options = _.extend(options, {async: false, success: callback});
            $.ajax(options);
        },

        throttleCheck: function (callback) {
            var options = this.createAjaxOptions('GET', urls.THROTTLE_CHECK);
            options = _.extend(options, {async: false, success: callback});
            $.ajax(options);
        },

        setAnalyticsOptIn: function (jsonPostData, successCallback, errorCallback) {
            var options = this.createAjaxOptions('POST', urls.ANALYTICS_OPT_IN);
            options = _.extend(options, {data: jsonPostData,
                                success: successCallback,
                                error: errorCallback,
                                contentType: "application/json; charset=utf-8",
                                dataType: "json"});
            $.ajax(options);
        }
    };

    return serverAPI;

});
