/*#include <math.h>*/

#include "ul_utmalloc.h"
#include "sui_dinfo.h"
#include "sui_event.h"
#include "sui_dtrans.h"

void sui_lintrans_par_inc_refcnt(sui_lintrans_par_t *par)
{
  if(!par) return;
  if( par->refcnt >= 0) par->refcnt++;
}

void sui_lintrans_par_dec_refcnt(sui_lintrans_par_t *par)
{
  if(!par) return;
  if(par->refcnt>0) par->refcnt--;
  if(!par->refcnt) {
    free(par);
  }
}

#ifdef CC_HAS_LONG_LONG

int sui_lintrans_mul_div(long *res, long val, long mul, long div)
{
  long long ll;
  unsigned long max=~0l;
  unsigned long max2=max/2;
  if(!div) return SUDI_DATA_ERR;
  if(!mul || !val) {
    *res=0;
    return SUDI_DATA_OK;
  }
  ll=val;
  ll*=mul;
  ll/=div;
  if(val>=max2)
    return SUDI_DATA_EOORP;
  if(val<=-max2)
    return SUDI_DATA_EOORN;
  *res=ll;
  return SUDI_DATA_OK;
}


#else /*CC_HAS_LONG_LONG*/

#define ul(p) ((unsigned long)p)

int sui_lintrans_mul_div(long *res, long val, long mul, long div)
{
  unsigned long rem;
  unsigned long max=~0l;
  unsigned long max2=max/2;
  unsigned long maxd;
  unsigned long umul;
  int sig;
  if(!div) return SUDI_DATA_ERR;
  if(!mul || !val) {
    *res=0;
    return SUDI_DATA_OK;
  }
  if(mul<0){sig=1; umul=-mul;}
  else { sig=0; umul=mul;}
  maxd=max2/umul;
  if((val<=maxd) || (val>=-maxd)){
    val*=mul;
    val/=div;
  } else {
    if(val<0) { val=-val; sig^=1; }
    if(div<0) { div=-div; sig^=1; }
    rem=ul(val)%ul(div);
    val=ul(val)/ul(div);
    if(val>=maxd)
      return sig?SUDI_DATA_EOORN:SUDI_DATA_EOORP;
    val=ul(val)*umul+(rem*umul)/ul(div);
    if(sig)
      val=-val;
  }
  *res=val;
  return SUDI_DATA_OK;
}

#endif /*CC_HAS_LONG_LONG*/

int sui_lintrans_proxy_rdval(sui_dinfo_t *dinfo, long indx, void *buf)
{
  int ret;
  long val;
  sui_dinfo_t *dfrom=(sui_dinfo_t*)(dinfo->ptr);
  sui_lintrans_par_t *par;
  if(!dfrom) return SUDI_DATA_NCON;
  if(!dfrom->rdval) return SUDI_DATA_ERR;
  ret=sui_rd_long(dfrom, indx, &val);
  if(ret!=SUDI_DATA_OK) return ret;

  par=(sui_lintrans_par_t*)dinfo->info;
  ret=sui_lintrans_mul_div(&val,val,par->multiply,par->divide);
  val+=par->offset;
  if(ret!=SUDI_DATA_OK) return ret;
  *(long*)buf=val;
  return SUDI_DATA_OK;
}

int sui_lintrans_proxy_limrdval(sui_dinfo_t *dinfo, long indx, void *buf)
{
  int ret;
  long val;
  ret=sui_lintrans_proxy_rdval(dinfo,indx,&val);
  if(ret!=SUDI_DATA_OK) return ret;
  if(val>dinfo->maxval)
    val=dinfo->maxval;
  if(val<dinfo->minval)
    val=dinfo->minval;
  *(long*)buf=val;
  return SUDI_DATA_OK;
}

int sui_lintrans_proxy_wrval(sui_dinfo_t *dinfo, long indx, const void *buf)
{
  int ret;
  long val;
  sui_dinfo_t *dfrom=(sui_dinfo_t*)(dinfo->ptr);
  sui_lintrans_par_t *par;
  if(!dfrom) return SUDI_DATA_NCON;
  if(!dfrom->wrval) return SUDI_DATA_ERR;
  val=*(long*)buf;

  par=(sui_lintrans_par_t*)dinfo->info;
  val-=par->offset;
  ret=sui_lintrans_mul_div(&val,val,par->divide,par->multiply);
  if(ret!=SUDI_DATA_OK) return ret;
  return sui_wr_long(dfrom, indx, &val);
}

void dinfo_lintrans_proxy_hevent(struct sui_dinfo *dinfo, struct sui_event *event)
{
  switch(event->what){
    case SUEV_COMMAND:
      if(event->u_ud()message.command!=SUCM_DONE) break;
    case SUEV_FREE:
      if(dinfo->info)
        sui_lintrans_par_dec_refcnt((sui_lintrans_par_t*)dinfo->info);
      dinfo->info=0;
      break;
  }
  dinfo_simple_proxy_hevent(dinfo,event);
}

sui_dinfo_t *dinfo_lintrans_proxy( sui_dinfo_t *dfrom, int afdig, long amin, long amax,
                              sui_lintrans_par_t *trans_par, int options)
{
  sui_dinfo_t *dinfo;
  sui_datai_rdfnc_t *rdfnc;
  
  if(options&SUI_TRANS_OPT_LIMRD)
    rdfnc=sui_lintrans_proxy_limrdval;
  else
    rdfnc=sui_lintrans_proxy_rdval;

  dinfo=sui_create_dinfo( dfrom, afdig, amin, amax, (long)trans_par,
                          rdfnc, sui_lintrans_proxy_wrval);
  if(dinfo==NULL) return NULL;
  sui_lintrans_par_inc_refcnt(trans_par);
  dinfo->hevent=dinfo_lintrans_proxy_hevent;
  dinfo->evmask=SUEV_COMMAND | SUEV_FREE;
  return dinfo;
}

