/*
  Copyright (C) 2004, 2005, 2008, 2011, 2017 Rocky Bernstein <rocky@gnu.org>
  Copyright (C) 1998 Monty xiphmont@mit.edu

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***
 *
 * Statistic code and cache management for overlap settings
 *
 ***/

#ifdef HAVE_CONFIG_H
# include "config.h"
# define __CDIO_CONFIG_H__ 1
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <cdio/paranoia/paranoia.h>
#include "p_block.h"
#include "overlap.h"
#include "isort.h"

/**** Internal cache management *****************************************/

void
paranoia_resetcache(cdrom_paranoia_t *p)
{
  c_block_t *c=c_first(p);
  v_fragment_t *v;

  while(c){
    free_c_block(c);
    c=c_first(p);
  }

  v=v_first(p);
  while(v){
    free_v_fragment(v);
    v=v_first(p);
  }
}

void
paranoia_resetall(cdrom_paranoia_t *p)
{
  p->root.returnedlimit=0;
  p->dyndrift=0;
  p->root.lastsector=0;

  if(p->root.vector){
    i_cblock_destructor(p->root.vector);
    p->root.vector=NULL;
  }

  paranoia_resetcache(p);
}

void
i_paranoia_trim(cdrom_paranoia_t *p, long int beginword, long int endword)
{
  root_block *root=&(p->root);
  if(root->vector!=NULL){
    long target=beginword-MAX_SECTOR_OVERLAP*CD_FRAMEWORDS;
    long rbegin=cb(root->vector);
    long rend=ce(root->vector);

    if(rbegin>beginword)
      goto rootfree;

    if(rbegin+MAX_SECTOR_OVERLAP*CD_FRAMEWORDS<beginword){
      if(target+MIN_WORDS_OVERLAP>rend)
	goto rootfree;

      {
	long int offset=target-rbegin;
	c_removef(root->vector,offset);
      }
    }

    {
      c_block_t *c=c_first(p);
      while(c){
	c_block_t *next=c_next(c);
	if(ce(c)<beginword-MAX_SECTOR_OVERLAP*CD_FRAMEWORDS)
	  free_c_block(c);
	c=next;
      }
    }

  }
  return;

rootfree:

  i_cblock_destructor(root->vector);
  root->vector=NULL;
  root->returnedlimit=-1;
  root->lastsector=0;

}

/**** Statistical and heuristic[al? :-] management ************************/

/* ===========================================================================
 * offset_adjust_settings (internal)
 *
 * This function is called by offset_add_value() every time 10 samples have
 * been accumulated.  This function updates the internal statistics for
 * paranoia (dynoverlap, dyndrift) that compensate for jitter and drift.
 *
 * (dynoverlap) influences how far stage 1 and stage 2 search for matching
 * runs.  In low-jitter conditions, it will be very small (or even 0),
 * narrowing our search.  In high-jitter conditions, it will be much larger,
 * widening our search at the cost of speed.
 *
 * ???: To be studied further.
 */
void
offset_adjust_settings(cdrom_paranoia_t *p,
		       void(*callback)(long int, paranoia_cb_mode_t))
{
  if(p->stage2.offpoints>=10){
    /* drift: look at the average offset value.  If it's over one
       sector, frob it.  We just want a little hysteresis [sp?]*/
    long av=(p->stage2.offpoints?p->stage2.offaccum/p->stage2.offpoints:0);

    if(labs(av)>p->dynoverlap/4){
      av=(av/MIN_SECTOR_EPSILON)*MIN_SECTOR_EPSILON;

      if(callback)(*callback)(ce(p->root.vector),PARANOIA_CB_DRIFT);
      p->dyndrift+=av;

      /* Adjust all the values in the cache otherwise we get a
	 (potentially unstable) feedback loop */
      {
	c_block_t *c=c_first(p);
	v_fragment_t *v=v_first(p);

	while(v && v->one){
	  /* safeguard beginning bounds case with a hammer */
	  if(fb(v)<av || cb(v->one)<av){
	    v->one=NULL;
	  }else{
	    fb(v)-=av;
	  }
	  v=v_next(v);
	}
	while(c){
	  long adj=min(av,cb(c));
	  c_set(c,cb(c)-adj);
	  c=c_next(c);
	}
      }

      p->stage2.offaccum=0;
      p->stage2.offmin=0;
      p->stage2.offmax=0;
      p->stage2.offpoints=0;
      p->stage2.newpoints=0;
      p->stage2.offdiff=0;
    }
  }

  if(p->stage1.offpoints>=10){
    /* dynoverlap: we arbitrarily set it to 4x the running difference
       value, unless min/max are more */

    p->dynoverlap=(p->stage1.offpoints?p->stage1.offdiff/
		   p->stage1.offpoints*3:CD_FRAMEWORDS);

    if(p->dynoverlap<-p->stage1.offmin*1.5)
      p->dynoverlap=-p->stage1.offmin*1.5;

    if(p->dynoverlap<p->stage1.offmax*1.5)
      p->dynoverlap=p->stage1.offmax*1.5;

    if(p->dynoverlap<MIN_SECTOR_EPSILON)p->dynoverlap=MIN_SECTOR_EPSILON;
    if(p->dynoverlap>MAX_SECTOR_OVERLAP*CD_FRAMEWORDS)
      p->dynoverlap=MAX_SECTOR_OVERLAP*CD_FRAMEWORDS;

    if(callback)(*callback)(p->dynoverlap,PARANOIA_CB_OVERLAP);

    if(p->stage1.offpoints>600){ /* bit of a bug; this routine is
				    called too often due to the overlap
				    mesh alg we use in stage 1 */
      p->stage1.offpoints/=1.2;
      p->stage1.offaccum/=1.2;
      p->stage1.offdiff/=1.2;
    }
    p->stage1.offmin=0;
    p->stage1.offmax=0;
    p->stage1.newpoints=0;
  }
}


/* ===========================================================================
 * offset_add_value (internal)
 *
 * This function adds the given jitter detected (value) to the statistics
 * for the given stage (o).  It is called whenever jitter has been identified
 * by stage 1 or 2.  After every 10 samples, we update the overall jitter-
 * compensation settings (e.g. dynoverlap).  This allows us to narrow our
 * search for matching runs (in both stages) in low-jitter conditions
 * and also widen our search appropriately when there is jitter.
 *
 *
 * "???BUG???:
 * Note that there is a bug in the way that this is called by try_sort_sync().
 * Silence looks like zero jitter, and dynoverlap may be incorrectly reduced
 * when there's lots of silence but also jitter."
 *
 * Strictly speaking, this is only sort-of true.  The silence will
 * incorrectly somewhat reduce dynoverlap.  However, it will increase
 * again once past the silence (even if reduced to zero, it will be
 * increased by the block read loop if we're not getting matches).
 * In reality, silence usually passes rapidly.  Anyway, long streaks
 * of silence benefit performance-wise from having dynoverlap decrease
 * momentarily. There is no correctness bug. --Monty
 *
 */
void
offset_add_value(cdrom_paranoia_t *p,offsets *o,long value,
		 void(*callback)(long int, paranoia_cb_mode_t))
{
  if(o->offpoints!=-1){

    /* Track the average magnitude of jitter (in either direction) */
    o->offdiff += labs(value);
    o->offpoints++;
    o->newpoints++;

    /* Track the net value of the jitter (to track drift) */
    o->offaccum+=value;

    /* Track the largest jitter we've encountered in each direction */
    if(value<o->offmin)o->offmin=value;
    if(value>o->offmax)o->offmax=value;

    /* After 10 samples, update dynoverlap, etc. */
    if(o->newpoints>=10)offset_adjust_settings(p,callback);
  }
}
