///////////////////////////////////////////////////////////////////////////////
// Run Actions Multiple Times.jsx
// V 1.0, last updated 2013-12-14
// This script allows you to run up to two actions multiple times. Very useful
// for altering animations.
//
// For more info and the latest version, go to
// http://wrestlingwithtext.com/giffing
//
// TODO: Documentation, some error handling for edge case situations.
// Currently the code can handle having n actions, so changing actionCnt
// to something greater than 2 would work just fine. Could easily add another
// field to allow users to define the number of actions to run each iteration.
// Could use some code clean-up and some best-practices... as soon as I
// figure out what those are.
///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
// MAIN SCRIPT:
///////////////////////////////////////////////////////////////////////////////


var dlg;
var stringOptions = [];

var actionOptnLists = [];
var actionCnt = 2;

function myRun() {
   var count = parseInt(dlg.edittext.text);

   var doc = activeDocument;
   for( var i = 0; i < count; i++ ) {
      for ( var k = 0; k < actionCnt; k++ ) {
         var actionIndex = parseInt(dlg['dropdownlist' + k].selection);
         var myStringOptions = actionOptnLists[k][actionIndex];
         if ( myStringOptions.action != undefined ) {
            app.doAction(myStringOptions.action.name, myStringOptions.actionSet.name);
         }
      }
   }

   setIntegerValue('frameCount', count);
   for ( var k = 0; k < actionCnt; k++ ) {
      var actionIndex = parseInt(dlg['dropdownlist' + k].selection);
      setIntegerValue('actionIndex' + k, actionIndex);
   }

   dlg.close(0);
}

function myShow() {
   var myFontSelection = "none";
   dlg = new Window ('dialog', 'Run action(s) multiple times');

   var item;

   var actionSets = GetActionSetInfo();

   for ( var k = 0; k < actionCnt; k++ ) {
      var dropdownlist = dlg['dropdownlist' + k] = dlg.add("dropdownlist");
      var stringOptions = [];
      item = dropdownlist.add ('item', 'Select an action:');
      stringOptions.push({});
      for ( var i = 0; i < actionSets.length; i++ ) {
         var actionSet = actionSets[i];
         if ( undefined != actionSet.children ) {
            item = dropdownlist.add('item', '' + actionSet.name);
            stringOptions.push({actionSet: actionSet});
            for ( var j = 0; j < actionSet.children.length; j++ ) {
               var action = actionSet.children[j];
               item = dropdownlist.add('item', '- ' + action.toString());
               stringOptions.push({action: action, actionSet: actionSet});
            }
         }
      }
      actionOptnLists.push(stringOptions);
      var actionIndex = getIntegerValue('actionIndex' + k, 0);

      dlg['dropdownlist' + k].selection = dlg['dropdownlist' + k].items[actionIndex];
   }

   dlg.cntgrp = dlg.add("group");
   dlg.cntgrp.add("statictext", undefined, "Times to run:");
   dlg.edittext = dlg.cntgrp.add("edittext", undefined, getIntegerValue('frameCount', 2));
   dlg.edittext.characters = 3;
   dlg.btnLyrs = dlg.cntgrp.add('button', undefined, 'Use layer count(' + activeDocument.layers.length + ')');

   dlg.btnLyrs.onClick = function() { 
      dlg.edittext.text = activeDocument.layers.length;
   }

   dlg.historyCheck = dlg.add("checkbox", undefined, 'Only one history state');
   if ( getIntegerValue('oneHistory', 1) ) dlg.historyCheck.value = true;

   dlg.btnRun = dlg.add('button', undefined , 'Run'); 

   dlg.btnRun.onClick = function() { 
      var actionIndex = parseInt(dlg.dropdownlist0.selection);
      var myStringOptions = actionOptnLists['0'][actionIndex];

      if ( myStringOptions.action == undefined ) {
         alert('You must select an action.' + ((myStringOptions.actionSet != undefined)?' You have selected an action set.':''));
      } else {
         var oneHistory = dlg.historyCheck.value;
         setIntegerValue('oneHistory', oneHistory ? 1 : 0 );
         if ( oneHistory ) {
            app.activeDocument.suspendHistory('Run ' + myStringOptions.action.name + ' ' + parseInt(dlg.edittext.text) + ' time' + ((parseInt(dlg.edittext.text) > 1) ? 's' : ''), 'myRun()');
         } else {
            myRun();
         }
      }
   };

   var suspendTextChange = false;
   dlg.edittext.onChange = function() {
      if ( suspendTextChange ) return;
      suspendTextChange = true;
      var val = parseInt(this.text);
      if ( isNaN(val) || !val ) val = 1;
      this.text = val;
      suspendTextChange = false;
   }

   dlg.orientation = 'column';

   dlg.center(); 
   dlg.show();
}

myShow();


///////////////////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS:
///////////////////////////////////////////////////////////////////////////////


function setIntegerValue(name, value) {
   value = parseInt(value);
   var desc = new ActionDescriptor();// create a descriptor to hold value
   desc.putInteger(0, value);
   app.putCustomOptions(name, desc, true );// store the descriptor by unique name
}

function getIntegerValue(name, dflt) {
   if ( dflt == undefined ) dflt = null;

   try{
      var desc = app.getCustomOptions(name);
   } catch(e) {
      return dflt;
   }

   if ( undefined != desc ) { // has counter stored
        return desc.getInteger(0);
   } else {// not stored so set default
        return dflt;
   }
}


///////////////////////////////////////////////////////////////////////////////
// Function: GetActionSetInfo
// Usage: walk all the items in the action palette and record the action set
//        names and all the action children
// Input: <none>
// Return: the array of all the ActionData
// Note: This will throw an error during a normal execution. There is a bug
// in Photoshop that makes it impossible to get an acurate count of the number
// of action sets.
///////////////////////////////////////////////////////////////////////////////
function GetActionSetInfo() {
   var actionSetInfo = new Array();
   var setCounter = 1;
     while ( true ) {
      var ref = new ActionReference();
      ref.putIndex( charIDToTypeID( 'ASet' ), setCounter );
      var desc = undefined;
      try { desc = executeActionGet( ref ); }
      catch( e ) { break; }
      var actionData = new ActionData();
      if ( desc.hasKey( charIDToTypeID( 'Nm  ' ) ) ) {
         actionData.name = desc.getString( charIDToTypeID( 'Nm  ' ) );
      }
      var numberChildren = 0;
      if ( desc.hasKey( charIDToTypeID( 'NmbC' ) ) ) {
         numberChildren = desc.getInteger( charIDToTypeID( 'NmbC' ) );
      }
      if ( numberChildren ) {
         actionData.children = GetActionInfo( setCounter, numberChildren );
         actionSetInfo.push( actionData );
      }
      setCounter++;
   }
   return actionSetInfo;
}


///////////////////////////////////////////////////////////////////////////////
// Function: GetActionInfo
// Usage: used when walking through all the actions in the action set
// Input: action set index, number of actions in this action set
// Return: true or false, true if file or folder is to be displayed
///////////////////////////////////////////////////////////////////////////////
function GetActionInfo( setIndex, numChildren ) {
   var actionInfo = new Array();
   for ( var i = 1; i <= numChildren; i++ ) {
      var ref = new ActionReference();
      ref.putIndex( charIDToTypeID( 'Actn' ), i );
      ref.putIndex(  charIDToTypeID( 'ASet' ), setIndex );
      var desc = undefined;
      desc = executeActionGet( ref );
      var actionData = new ActionData();
      if ( desc.hasKey( charIDToTypeID( 'Nm  ' ) ) ) {
         actionData.name = desc.getString( charIDToTypeID( 'Nm  ' ) );
      }
      var numberChildren = 0;
      if ( desc.hasKey( charIDToTypeID( 'NmbC' ) ) ) {
         numberChildren = desc.getInteger( charIDToTypeID( 'NmbC' ) );
      }
      actionInfo.push( actionData );
   }
   return actionInfo;
}

///////////////////////////////////////////////////////////////////////////////
// Function: ActionData
// Usage: this could be an action set or an action
// Input: <none>
// Return: a new Object of ActionData
///////////////////////////////////////////////////////////////////////////////
function ActionData() {
   this.name = "";
   this.children = undefined;
   this.toString = function () {
      var strTemp = this.name;
      if ( undefined != this.children ) {
         for ( var i = 0; i < this.children.length; i++ ) {
            strTemp += "\n" + this.children[i].toString();
         }
      }
      return strTemp;
   }
}
