/*
*Copyright(c)2016, Jeffrey Lee
*Allrightsreserved.
*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
*/

#include "profiling.h"
#include "debug.h"
#include <kernel.h>
#include <oslib/hal.h>

#ifdef PROFILING_ENABLED

static profile_struct_t root = { "Other" };

static profile_struct_t *full_list;
static profile_struct_t *active_list;

static hal_timer profile_timer = 0;
static uint32_t timer_period;
static float sec_scale;

void profile_init()
{
  full_list = active_list = &root; /* GCC 4.1.2 bug - can't have these staticly initialised to point to root */
  /* Read timer max period, and configure for that */
  if (profile_timer)
  {
    xhal_timer_max_period(profile_timer, &timer_period);
    dprintf("timer max period: %u\n",timer_period);
    xhal_timer_set_period(profile_timer, timer_period);
  }
  else
  {
    xhal_timer_period(profile_timer, &timer_period);
    dprintf("timer period: %u\n",timer_period);    
  }
  uint32_t gran;
  xhal_timer_granularity(profile_timer,&gran);
  dprintf("granularity: %u\n",gran);
  sec_scale = 1.0f/gran;
}

void profile_update()
{
  static int tick_count;

  if (++tick_count != 100)
  {
    return;
  }
  tick_count = 0;
  profile_struct_t *prof = full_list;
  while (prof)
  {
    if (prof->cumulative_time)
    {
      float secs = sec_scale * prof->cumulative_time;
      if (prof->cumulative_metric)
      {
        float metric = prof->cumulative_metric / secs;
        dprintf("%s: %u, %.2fms, %.2f/sec\n", prof->name, prof->cumulative_count, secs*1000.0f, metric);
      }
      else
      {
        dprintf("%s: %u, %.2fms\n", prof->name, prof->cumulative_count, secs*1000.0f);
      }
    }
    prof->cumulative_count = 0;
    prof->cumulative_time = 0;
    prof->cumulative_metric = 0;
    prof = prof->full_list;
  }
}

static uint64_t calc_elapsed()
{
  static uint32_t prev;
  uint32_t now;
  xhal_timer_read_countdown(profile_timer,&now);
  uint64_t elapsed = ((uint64_t) prev) - now;
  if (prev < now)
  {
    /* Must have wrapped */
    elapsed += timer_period;
  }
  prev = now;
  return elapsed;
}

void profile_begin(profile_struct_t *prof)
{
  if (!prof->full_list)
  {
    prof->full_list = full_list;
    full_list = prof;
  }
  uint64_t elapsed = calc_elapsed();
  active_list->cumulative_time += elapsed;
  prof->active_list = active_list;
  active_list = prof;
  prof->cumulative_count++;
}

void profile_end(profile_struct_t *prof)
{
  /* Assume prof == active_list */
  uint64_t elapsed = calc_elapsed();
  active_list->cumulative_time += elapsed;
  active_list = active_list->active_list;
}

#endif
