/**************************************************************\
*                    Animation Library                         *
*                        by Blargel                            *
*                                                              *
* Please look at the comments above each function name for     *
* instructions on how to use each function. Here is a general  *
* overview, though, of the features of this animation library. *
*                                                              *
* CreateAnimation will let you name a sprite and associated    *
* animation with it as well as define how many frames it has.  *
*                                                              *
* SetAnimationFrame will let you define how long each frame in *
* the animation lasts, which image to use, and what the        *
* graphic rectangles are.                                      *
*                                                              *
* Animate handles the logic for you with the information you   *
* provided in the CreateAnimation and SetAnimationFrame        *
* functions.                                                   *
*                                                              *
* DrawAnimatedSprite draws the specided animated sprite at the *
* specified point. It automatically calls SetTexture,          *
* SetGraphicRect, and DrawGraphic for you.                     *
*                                                              *
*                                                              *
* The other functions are functions I wrote to help me run the *
* library correctly. Unless you absolutely know what you are   *
* doing and perfectly understand what I did, I highly suggest  *
* not using them by yourself when using this library.          *
\**************************************************************/


// This variable stores the maximum number of frames that an animation
// can store. As you can see, it defaults to 16. You can override this
// by calling MaxAnimationFrames = somevalue; in @Initalize or where you
// normally declare variables if you need more frames, or want to reduce
// the number of empty frames to shorten processing time.
//
// I highly advise you not to change it in the library itself but you can
// if you really want to.
let MaxAnimationFrames = 16;




// Variables that you should never touch when using this library.
// Yes, they're all arrays.
// No, I'm not crazy. :V
let SpriteNames = [];

let AnimationNames = [];
let AnimationMaxFrames = [];
let AnimationFilePaths = [];
let AnimationFrames = [];

let CurrentGraphicRect = [];
let CurrentAnimation = [];
let CurrentAnimationFrame = [];
let CurrentFilePath = [];


// Creates an animation with the specified name for the specified sprite. If the
// sprite name does not exist yet, it will create it automatically.
// Parameter 1: Sprite name (string)
// Parameter 2: Animation name (string)
// Parameter 3: Number of frames in the animation (integer)
function CreateAnimation(spritename, animname, frames) {
  assert(frames>0, "You must have more than one frame. Animation: " ~ animname);
  assert(frames%1==0, "You must have an integer for number of frames. Animation: " ~ animname);
  assert(animname != "nil", "You cannot have an animation called nil");
  assert(frames<=MaxAnimationFrames, "You have exceeded the maximum number of frames allowed in a single animation.");
  AnimationNames = AnimationNames ~ [[spritename, animname]];
  AnimationMaxFrames = AnimationMaxFrames ~ [frames];
  AnimationFilePaths = AnimationFilePaths ~ [emptyFilePaths(MaxAnimationFrames)];
  AnimationFrames = AnimationFrames ~ [emptyFrames(MaxAnimationFrames)];
  
  if(!spriteNameExists(spritename)){
    SpriteNames = SpriteNames ~ [spritename];
    CurrentGraphicRect = CurrentGraphicRect ~ [[0, 0, 0, 0]];
    CurrentAnimation = CurrentAnimation ~ ["nil"];
    CurrentAnimationFrame = CurrentAnimationFrame ~ [0];
    CurrentFilePath = CurrentFilePath ~ ["nil"];
  }
}

// Sets the individual frames in an animation. All frames must be set before calling
// Animate or an error will be raised.
// Parameter 1: Sprite name (string)
// Parameter 2: Animation name (string)
// Parameter 3: Animation frame number (integer), starts at 0
// Parameter 4: Image file path (string)
// Parameter 5: Duration of animation frame (integer)
// Parameter 6: Left bound of graphic rectangle.
// Parameter 7: Top bound of graphic rectangle.
// Parameter 8: Right bound of graphic rectangle.
// Parameter 9: Bottom bound of graphic rectangle.
function SetAnimationFrame(spritename, animname, framenumber, imagefilepath, frameduration, left, top, right, bottom){
  let indx = getAnimationIndex(spritename, animname);
  assert(framenumber>=0 && framenumber%1==0 && framenumber<=AnimationMaxFrames[indx] - 1, ToString(framenumber) ~ " is not a valid animation frame for animation: " ~ animname);
  assert(frameduration>0 && frameduration%1==0, ToString(frameduration) ~ " is not a valid animation frame duration for animation: " ~ animname);
  AnimationFilePaths = Array2DSetValue(AnimationFilePaths, indx, framenumber, imagefilepath);
  AnimationFrames = Array2DSetValue(AnimationFrames, indx, framenumber, [frameduration, left, top, right, bottom]);
}

// Starts the logic of the indicated animation for the indicated sprite.
// Does NOT do the actual drawing, only the logic. If looping is set to
// false, the last animation frame is held until Animate is called again.
// If Animate is called while another Animate for the same sprite is
// already running, the new Animate will have priority and the old
// Animate will stop running.
// Parameter 1: Sprite name (string)
// Parameter 2: Animation name (string)
// Parameter 3: Looping animation? (true/false)
task Animate(spritename, animname, looping){
  let animindx = getAnimationIndex(spritename, animname);
  let spriteindx = getSpriteIndex(spritename);
  let Frames = AnimationFrames[animindx];
  
  CurrentAnimation[spriteindx] = animname;
  
  while(CurrentAnimation[spriteindx] == animname){
    CurrentAnimationFrame[spriteindx] = 0;
    ascent(i in 0..AnimationMaxFrames[animindx]){
      assert(Frames[i][0]>=0, "You did not define all animation frames in animation: " ~ animname);
      CurrentGraphicRect[spriteindx] = [Frames[i][1], Frames[i][2], Frames[i][3], Frames[i][4]];
      CurrentFilePath[spriteindx] = AnimationFilePaths[animindx][i];
      loop(Frames[i][0]){
        yield ;
        if(CurrentAnimation[spriteindx] != animname){
          CurrentAnimationFrame[spriteindx] = 0;
          return ;
        }
      }
      CurrentAnimationFrame[spriteindx] = CurrentAnimationFrame[spriteindx] + 1;
    }
    if(!looping){
      CurrentAnimation[spriteindx] = "nil";
    }
  }
}

// Draws the specified sprite at the specified position. Note that
// if Animate has not been called for the sprite yet, nothing will
// be drawn.
// Parameter 1: Sprite name (string)
// Parameter 2: x-coordinate (float)
// Parameter 3: y-coordinate (float)
function DrawAnimatedSprite(spritename, x, y){
  let indx = getSpriteIndex(spritename);
  SetTexture(CurrentFilePath[indx]);
  SetGraphicRect(CurrentGraphicRect[indx][0], CurrentGraphicRect[indx][1], CurrentGraphicRect[indx][2], CurrentGraphicRect[indx][3]);
  DrawGraphic(x, y);
}



// Private Functions
// Just functions I wrote that help the library run correctly.

function spriteNameExists(spritename){
  ascent(i in 0..length(SpriteNames)){
    if(spritename == SpriteNames[i]){
      return true;
    }
  }
  return false;
}
  
function Array2DSetValue(a,x,y,v){
  let t = a[x];
  t[y] = v;
  a[x] = t;
  return a;
}

function emptyFrames(num){
  assert(num>0, "MaxAnimationFrames must be greater than 0.");
  assert(num%1==0, "MaxAnimationFrames must be an integer.");
  let array = [];
  loop(num){
    array = array ~ [[-1, 0, 0, 0, 0]];
  }
  return array;
}

function emptyFilePaths(num){
  assert(num>0, "MaxAnimationFrames must be greater than 0.");
  assert(num%1==0, "MaxAnimationFrames must be an integer.");
  let array = [];
  loop(num){
    array = array ~ ["nil"]
  }
  return array;
}

function getSpriteIndex(spritename){
  ascent(i in 0..length(SpriteNames)){
    if(spritename == SpriteNames[i]){
      return i;
    }
  }
  assert(false, "Sprite with name of " ~ spritename ~ " cannot be found.");
}

function getAnimationIndex(spritename, animname){
  ascent(i in 0..length(AnimationNames)){
    if([spritename, animname] == AnimationNames[i]){
      return i;
    }
  }
  assert(false, "Animation with sprite name of " ~ spritename ~ " and animation name of " ~ animname ~ " cannot be found");
}