/**************************************************************************************************
*
* ADOBE SYSTEMS INCORPORATED
* Copyright 2013 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
* terms of the Adobe license agreement accompanying it.  If you have received this file from a
* source other than Adobe, then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*
**************************************************************************************************/

/** Vulcan - v7.0.0 */

/**
 * @class Vulcan
 *
 * The singleton instance, <tt>VulcanInterface</tt>, provides an interface
 * to the Vulcan. Allows you to launch CC applications
 * and discover information about them.
 */
function Vulcan()
{
}

/**
 * Gets all available application specifiers on the local machine.
 *
 * @return The array of all available application specifiers.
 */
Vulcan.prototype.getTargetSpecifiers = function()
{	
	var params = {};
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetTargetSpecifiers", JSON.stringify(params)));
};

/**
 * Launches a CC application on the local machine, if it is not already running.
 *
 * @param targetSpecifier The application specifier; for example "indesign".
 *
 *        Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
 *        and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
 *        installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
 *        receive wrong result.
 *        The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
 *
 *        In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
 * @param focus           True to launch in foreground, or false to launch in the background.
 * @param cmdLine         Optional, command-line parameters to supply to the launch command.
 * @return True if the app can be launched, false otherwise.
 */
Vulcan.prototype.launchApp = function(targetSpecifier, focus, cmdLine)
{
    if(!requiredParamsValid(targetSpecifier))
    {
        return false;
    }
	
	var params = {};
	params.targetSpecifier = targetSpecifier;
	params.focus = focus ? "true" : "false";
	params.cmdLine = requiredParamsValid(cmdLine) ? cmdLine : "";
	
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanLaunchApp", JSON.stringify(params))).result;
};

/**
 * Checks whether a CC application is running on the local machine.
 *
 * @param targetSpecifier The application specifier; for example "indesign".
 *
 *        Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
 *        and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
 *        installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
 *        receive wrong result.
 *        The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
 *
 *        In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
 * @return True if the app is running, false otherwise.
 */
Vulcan.prototype.isAppRunning = function(targetSpecifier)
{
    if(!requiredParamsValid(targetSpecifier))
    {
        return false;
    }
	
	var params = {};
	params.targetSpecifier = targetSpecifier;
	
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanIsAppRunning", JSON.stringify(params))).result;
};

/**
 * Checks whether a CC application is installed on the local machine.
 *
 * @param targetSpecifier The application specifier; for example "indesign".
 *
 *        Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
 *        and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
 *        installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
 *        receive wrong result.
 *        The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
 *
 *        In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
 * @return True if the app is installed, false otherwise.
 */
Vulcan.prototype.isAppInstalled = function(targetSpecifier)
{
    if(!requiredParamsValid(targetSpecifier))
    {
        return false;
    }
	
	var params = {};
	params.targetSpecifier = targetSpecifier;
	
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanIsAppInstalled", JSON.stringify(params))).result;
};

/**
 * Retrieves the local install path of a CC application.
 *
 * @param targetSpecifier The application specifier; for example "indesign".
 *
 *        Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version
 *        and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you
 *        installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may
 *        receive wrong result.
 *        The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator".
 *
 *        In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier.
 * @return The path string if the application is found, "" otherwise.
 */
Vulcan.prototype.getAppPath = function(targetSpecifier)
{
    if(!requiredParamsValid(targetSpecifier))
    {
        return "";
    }
	
	var params = {};
	params.targetSpecifier = targetSpecifier;
	
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetAppPath", JSON.stringify(params))).result;
};

/**
 * Registers a message listener callback function for a Vulcan message.
 *
 * @param type            The message type.
 * @param callback        The callback function that handles the message.
 *                        Takes one argument, the message object.
 * @param obj             Optional, the object containing the callback method, if any.
 *                        Default is null.
 */
Vulcan.prototype.addMessageListener = function(type, callback, obj)
{
    if(!requiredParamsValid(type, callback) || !strStartsWith(type, VulcanMessage.TYPE_PREFIX))
    {
        return;
    }

	var params = {};
	params.type = type;
	
	window.__adobe_cep__.invokeAsync("vulcanAddMessageListener", JSON.stringify(params), callback, obj);
};

/**
 * Removes a registered message listener callback function for a Vulcan message.
 *
 * @param type            The message type.
 * @param callback        The callback function that was registered.
 *                        Takes one argument, the message object.
 * @param obj             Optional, the object containing the callback method, if any.
 *                        Default is null.
 */
Vulcan.prototype.removeMessageListener = function(type, callback, obj)
{
    if(!requiredParamsValid(type, callback) || !strStartsWith(type, VulcanMessage.TYPE_PREFIX))
    {
        return;
    }
	
    var params = {};
    params.type = type;
	
    window.__adobe_cep__.invokeAsync("vulcanRemoveMessageListener", JSON.stringify(params), callback, obj);
};

/**
 * Dispatches a Vulcan message.
 *
 * @param vulcanMessage   The message object.
 */
Vulcan.prototype.dispatchMessage = function(vulcanMessage)
{
    if(!requiredParamsValid(vulcanMessage) || !strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX))
    {
        return;
    }

	var params = {};
	var message = new VulcanMessage(vulcanMessage.type);
	message.initialize(vulcanMessage);
	params.vulcanMessage = message;
	
	window.__adobe_cep__.invokeSync("vulcanDispatchMessage", JSON.stringify(params));
};

/**
 * Retrieves the message payload of a Vulcan message for the registered message listener callback function.
 *
 * @param vulcanMessage   The message object.
 * @return                A string containing the message payload.
 */
Vulcan.prototype.getPayload = function(vulcanMessage)
{
    if(!requiredParamsValid(vulcanMessage) || !strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX))
    {
        return null;
    }

	var message = new VulcanMessage(vulcanMessage.type);
	message.initialize(vulcanMessage);
	return message.getPayload();
};

/**
 * Gets all available endpoints of the running Vulcan-enabled applications.
 *
 * Since 7.0.0
 *
 * @return                The array of all available endpoints.
 * An example endpoint string:
 * <endPoint>
 *   <appId>PHXS</appId>
 *   <appVersion>16.1.0</appVersion>
 * </endPoint>
 */
Vulcan.prototype.getEndPoints = function()
{
	var params = {};
	return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetEndPoints", JSON.stringify(params)));
};

/**
 * Gets the endpoint for itself.
 *
 * Since 7.0.0
 *
 * @return                The endpoint string for itself.
 */
Vulcan.prototype.getSelfEndPoint = function()
{
	var params = {};
	return window.__adobe_cep__.invokeSync("vulcanGetSelfEndPoint", JSON.stringify(params));
};

/** Singleton instance of Vulcan **/
var VulcanInterface = new Vulcan();

//--------------------------------- Vulcan Message ------------------------------

/**
 * @class VulcanMessage
 * Message type for sending messages between host applications.
 * A message of this type can be sent to the designated destination
 * when appId and appVersion are provided and valid. Otherwise,
 * the message is broadcast to all running Vulcan-enabled applications.
 *
 * To send a message between extensions running within one
 * application, use the <code>CSEvent</code> type in CSInterface.js.
 *
 * @param type            The message type.
 * @param appId           The peer appId.
 * @param appVersion      The peer appVersion.
 *
 */
function VulcanMessage(type, appId, appVersion)
{
    this.type = type;
    this.scope = VulcanMessage.SCOPE_SUITE;
    this.appId = requiredParamsValid(appId) ? appId : VulcanMessage.DEFAULT_APP_ID;
	this.appVersion = requiredParamsValid(appVersion) ? appVersion : VulcanMessage.DEFAULT_APP_VERSION;
    this.data = VulcanMessage.DEFAULT_DATA;
}

VulcanMessage.TYPE_PREFIX    = "vulcan.SuiteMessage.";
VulcanMessage.SCOPE_SUITE    = "GLOBAL";
VulcanMessage.DEFAULT_APP_ID = "UNKNOWN";
VulcanMessage.DEFAULT_APP_VERSION = "UNKNOWN";
VulcanMessage.DEFAULT_DATA = "<data><payload></payload></data>";
VulcanMessage.dataTemplate = "<data>{0}</data>";
VulcanMessage.payloadTemplate = "<payload>{0}</payload>";

/**
 * Initializes this message instance.
 *
 * @param message         A \c message instance to use for initialization.
 */
VulcanMessage.prototype.initialize = function(message)
{
    this.type = message.type;
    this.scope = message.scope;
    this.appId = message.appId;
    this.appVersion = message.appVersion;
    this.data = message.data;
};

/**
 * Retrieves the message data.
 *
 * @return A data string in XML format.
 */
VulcanMessage.prototype.xmlData = function()
{
    if(this.data === undefined)
    {
        var str = "";
		str = String.format(VulcanMessage.payloadTemplate, str);
		this.data = String.format(VulcanMessage.dataTemplate, str);
    }
    return this.data;
};

/**
 * Sets the message payload of this message.
 *
 * @param payload         A string containing the message payload.
 */
VulcanMessage.prototype.setPayload = function(payload)
{
    var str = cep.encoding.convertion.utf8_to_b64(payload);
    str = String.format(VulcanMessage.payloadTemplate, str);
    this.data = String.format(VulcanMessage.dataTemplate, str);
};

/**
 * Retrieves the message payload of this message.
 *
 * @return                A string containing the message payload.
 */
VulcanMessage.prototype.getPayload = function()
{
    var str = GetValueByKey(this.data, "payload");
    if(str !== null)
    {
        return cep.encoding.convertion.b64_to_utf8(str);
    }
    return null;
};

/**
 * Converts the properties of this instance to a string.
 *
 * @return The string version of this instance.
 */
VulcanMessage.prototype.toString = function()
{
    var str = "type=" + this.type;
    str += ", scope=" + this.scope;
    str += ", appId=" + this.appId;
    str += ", appVersion=" + this.appVersion;
    str += ", data=" + this.xmlData();
    return str;
};

//--------------------------------------- Util --------------------------------

/**
 * Formats a string based on a template.
 *
 * @param src The format template.
 *
 * @return The formatted string
 */
String.format = function(src)
{
    if (arguments.length === 0)
    {
        return null;
    }

    var args = Array.prototype.slice.call(arguments, 1);
    return src.replace(/\{(\d+)\}/g, function(m, i){
        return args[i];
  });
};

/**
 * Retrieves the content of an XML element.
 *
 * @param xmlStr    The XML string.
 * @param key       The name of the tag.
 *
 * @return          The content of the tag, or the empty string
 *                  if such tag is not found or the tag has no content.
 */
function GetValueByKey(xmlStr, key)
{
    if(window.DOMParser)
    {
        var parser = new window.DOMParser();
        try
        {
            var xmlDoc = parser.parseFromString(xmlStr, "text/xml");
            var node = xmlDoc.getElementsByTagName(key)[0];
            if(node && node.childNodes[0])
            {
                return node.childNodes[0].nodeValue;
            }
        }
        catch(e)
        {
            //log the error
        }
    }
    return "";
}

/**
 * Reports whether required parameters are valid.
 *
 * @return    True if all required parameters are valid,
 *            false if any of the required parameters are invalid.
 */
function requiredParamsValid()
{
    for(var i = 0; i < arguments.length; i++)
    {
        var argument = arguments[i];
        if(argument === undefined || argument === null)
        {
            return false;
        }
    }
    return true;
}

/**
 * Reports whether a string has a given prefix.
 *
 * @param str       The target string.
 * @param prefix    The specific prefix string.
 *
 * @return          True if the string has the prefix, false if not.
 */
function strStartsWith(str, prefix)
{
    if(typeof str != "string")
    {
        return false;
    }
    return str.indexOf(prefix) === 0;
}
