//  DescriptionHere - curves.cpp
//  Created by John Ryland (jryland@xiaofrog.com), 05/11/2017
//  Copyright (c) 2017 InvertedLogic
//  All rights reserved.
#include <math.h>
#include <map>
#include <string>
#include "curves.h"

typedef float (*CurveFunction)(float ratio);

namespace details
{

class CurveFunctionMap
{
public:
  CurveFunctionMap() {
    map("square",           &squareCurve);
    map("linear",           &linearCurve);
    map("easeIn",           &easeInCurve);
    map("easeOut",          &easeOutCurve);
    map("easeInOut",        &easeInOutCurve);
    map("easeOutIn",        &easeOutInCurve);
    map("easeInBack",       &easeInBackCurve);
    map("easeOutBack",      &easeOutBackCurve);
    map("easeInOutBack",    &easeInOutBackCurve);
    map("easeOutInBack",    &easeOutInBackCurve);
    map("easeInBounce",     &easeInBounceCurve);
    map("easeOutBounce",    &easeOutBounceCurve);
    map("easeInOutBounce",  &easeInOutBounceCurve);
    map("easeOutInBounce",  &easeOutInBounceCurve);
    map("easeInElastic",    &easeInElasticCurve);
    map("easeOutElastic",   &easeOutElasticCurve);
    map("easeInOutElastic", &easeInOutElasticCurve);
    map("easeOutInElastic", &easeOutInElasticCurve);
  }
  void map(const char* str, CurveFunction func) {
    m_map.insert(std::make_pair(str, func));
  }
  std::map<std::string, CurveFunction> m_map;
};

}

bool lookupCurveFunction(const char* curveName, CurveFunction& curveFunction)
{
  static details::CurveFunctionMap s_curveFunctionMap;
  if (s_curveFunctionMap.m_map.count(curveName)) {
    curveFunction = s_curveFunctionMap.m_map[curveName];
    return true;
  }
  return false;
}

void getCurveFunctionNames(std::vector<std::string>& a_names)
{
  static details::CurveFunctionMap s_curveFunctionMap;
  typedef std::map<std::string, CurveFunction>::iterator itT;
  for (itT it = s_curveFunctionMap.m_map.begin(); it != s_curveFunctionMap.m_map.end(); ++it)
    a_names.push_back(it->first);
}

float evaluateCurve(const char* curveName, float ratio)
{
  CurveFunction curveFunction;
  if (lookupCurveFunction(curveName, curveFunction))
    return curveFunction(ratio);
  return 1.0f;
}

inline float square(float ratio)
{
  //return powf(ratio, 2.0f);
  return ratio * ratio;
}

inline float inOutHelper(float ratio, CurveFunction inCurve, CurveFunction outCurve)
{
  if (ratio < 0.5f)
    return 0.5f * inCurve(ratio*2.0f);
  return 0.5f * outCurve((ratio-0.5f)*2.0f) + 0.5f;
}

float squareCurve(float ratio)
{
  if (ratio < 0.5f)
    return 0.0f;
  return 1.0f;
}

float linearCurve(float ratio)
{
  return ratio;
}

float easeInCurve(float ratio)
{
  return ratio * ratio * ratio;
}

float easeOutCurve(float ratio)
{
  float invRatio = ratio - 1.0f;
  return invRatio * invRatio * invRatio + 1.0f;
}

float easeInOutCurve(float ratio)
{
  return inOutHelper(ratio, easeInCurve, easeOutCurve);
}

float easeOutInCurve(float ratio)
{
  return inOutHelper(ratio, easeOutCurve, easeInCurve);
}

float easeInBackCurve(float ratio)
{
  float s = 1.70158f;
  return square(ratio) * ((s + 1.0f)*ratio - s);    
}

float easeOutBackCurve(float ratio)
{
  float invRatio = ratio - 1.0f;
  float s = 1.70158f;
  return square(invRatio) * ((s + 1.0f)*invRatio + s) + 1.0f;    
}

float easeInOutBackCurve(float ratio)
{
  return inOutHelper(ratio, easeInBackCurve, easeOutBackCurve);
}

float easeOutInBackCurve(float ratio)
{
  return inOutHelper(ratio, easeOutBackCurve, easeInBackCurve);
}

float easeInBounceCurve(float ratio)
{
  return 1.0f - easeOutBounceCurve(1.0f - ratio);
}

float easeOutBounceCurve(float ratio)
{
  const float s = 7.5625f;
  const float p = 2.75f;
  if (ratio < (1.0f/p))
    return s * square(ratio);
  if (ratio < (2.0f/p))
    return s * square(ratio - 1.5f/p) + 0.75f;
  if (ratio < 2.5f/p)
    return s * square(ratio - 2.25f/p) + 0.9375f;
  return s * square(ratio - 2.625f/p) + 0.984375f;
}

float easeInOutBounceCurve(float ratio)
{
  return inOutHelper(ratio, easeInBounceCurve, easeOutBounceCurve);
}

float easeOutInBounceCurve(float ratio)
{
  return inOutHelper(ratio, easeOutBounceCurve, easeInBounceCurve);
}

float easeInElasticCurve(float ratio)
{
  if (ratio == 0.0f || ratio == 1.0f)
    return ratio;
  float p = 0.3f;
  float s = p / 4.0f;
  float invRatio = ratio - 1.0f;
  return -1.0f * powf(2.0f, 10.0f*invRatio) * sinf((invRatio-s)*2.0f*M_PI/p);        
}

float easeOutElasticCurve(float ratio)
{
  if (ratio == 0.0f || ratio == 1.0f)
    return ratio;
  float p = 0.3f;
  float s = p / 4.0f;
  return powf(2.0f, -10.0f*ratio) * sinf((ratio-s)*2.0f*M_PI/p) + 1.0f;
}

float easeInOutElasticCurve(float ratio)
{
  return inOutHelper(ratio, easeInElasticCurve, easeOutElasticCurve);
}

float easeOutInElasticCurve(float ratio)
{
  return inOutHelper(ratio, easeOutElasticCurve, easeInElasticCurve);
}


