/***************************************************************************
                                 enodes.cpp
                                 ----------      
    begin                : Feb 12 2004
    copyright            : (C) 2004 The University of Toronto
    email                :
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <assert.h>
#include <math.h>
//#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <kdebug.h>
#include <klocale.h>

#include "enodes.h"
#include "kstdatacollection.h"
#include "kstdebug.h"
#include "kstmath.h"
#include "plugincollection.h"

// FIXME: merge this with the one is KstVector, but be careful not to
//        introduce -any- new instructions generated by the compiler.
static double interpolate(double *_v, int _size, int in_i, int ns_i);

using namespace Equation;

Node::Node() {
}


Node::~Node() {
}


void Node::collectVectors(KstVectorList&) {
}


void Node::visit(NodeVisitor* v) {
  v->visitNode(this);
}


KstObject::UpdateType Node::update(int counter, Context *ctx) {
  Q_UNUSED(counter)
  Q_UNUSED(ctx)
  return KstObject::NO_CHANGE;
}


/////////////////////////////////////////////////////////////////
BinaryNode::BinaryNode(Node *left, Node *right)
: Node(), _left(left), _right(right) {
}


BinaryNode::~BinaryNode() {
  delete _left;
  _left = 0L;
  delete _right;
  _right = 0L;
}


void BinaryNode::collectVectors(KstVectorList& c) {
  _left->collectVectors(c);
  _right->collectVectors(c);
}


void BinaryNode::visit(NodeVisitor* v) {
  v->visitBinaryNode(this);
}


Node *& BinaryNode::left() {
  return _left;
}


Node *& BinaryNode::right() {
  return _right;
}


KstObject::UpdateType BinaryNode::update(int counter, Context *ctx) {
  KstObject::UpdateType l = _left->update(counter, ctx);
  KstObject::UpdateType r = _right->update(counter, ctx);
  return (l == KstObject::UPDATE || r == KstObject::UPDATE) ? KstObject::UPDATE : KstObject::NO_CHANGE;
}


/////////////////////////////////////////////////////////////////
Addition::Addition(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Addition: %p + %p\n", (void*)this, (void*)left, (void*)right);
}


Addition::~Addition() {
}


double Addition::value(Context *ctx) {
  return _left->value(ctx) + _right->value(ctx);
}


bool Addition::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
Subtraction::Subtraction(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Subtraction: %p - %p\n", (void*)this, (void*)left, (void*)right);
}


Subtraction::~Subtraction() {
}


double Subtraction::value(Context *ctx) {
  return _left->value(ctx) - _right->value(ctx);
}


bool Subtraction::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
Multiplication::Multiplication(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Multiplication: %p - %p\n", (void*)this, (void*)left, (void*)right);
}


Multiplication::~Multiplication() {
}


double Multiplication::value(Context *ctx) {
  return _left->value(ctx) * _right->value(ctx);
}


bool Multiplication::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
Division::Division(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Division: %p - %p\n", (void*)this, (void*)left, (void*)right);
}


Division::~Division() {
}


double Division::value(Context *ctx) {
  return _left->value(ctx) / _right->value(ctx);
}


bool Division::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
Modulo::Modulo(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Modulo: %p - %p\n", (void*)this, (void*)left, (void*)right);
}


Modulo::~Modulo() {
}


double Modulo::value(Context *ctx) {
  return fmod(_left->value(ctx), _right->value(ctx));
}


bool Modulo::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
Power::Power(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Power: %p - %p\n", (void*)this, (void*)left, (void*)right);
}


Power::~Power() {
}


double Power::value(Context *ctx) {
  return pow(_left->value(ctx), _right->value(ctx));
}


bool Power::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////

static double cot(double x) {
  return 1.0/tan(x);
}


static double csc(double x) {
  return 1.0/sin(x);
}


static double sec(double x) {
  return 1.0/cos(x);
}


static struct {
  const char *name;
  double (*func)(double);
} FTable[] = {
  {"abs",  &fabs},
  {"acos", &acos},
  {"asin", &asin},
  {"atan", &atan},
  {"cbrt", &cbrt},
  {"cos",  &cos},
  {"cosh", &cosh},
  {"cot",  &cot},
  {"csc",  &csc},
  {"exp",  &exp},
  {"log",  &log10},
  {"ln",   &log},
  {"sec",  &sec},
  {"sin",  &sin},
  {"sinh", &sinh},
  {"sqrt", &sqrt},
  {"tan",  &tan},
  {"tanh", &tanh},
  {0, 0}
};


Function::Function(char *name, ArgumentList *args)
: Node(), _name(name), _args(args), _f(0L), _plugin(0L) {
  _inPid = 0L;
  _inScalars = 0L;
  _inVectors = 0L;
  _outScalars = 0L;
  _outVectors = 0L;
  _inArrayLens = 0L;
  _outArrayLens = 0L;
  _outputIndex = -424242;
  _localData = 0L;
  _outputVectorCnt = 0;
  _inputVectorCnt = 0;
  //printf("%p: New Function: %s - %p\n", (void*)this, name, (void*)args);
  if (strcasecmp("plugin", name) == 0) {
    Identifier *pn = dynamic_cast<Identifier*>(_args->node(0));
    if (pn) {
      _plugin = PluginCollection::self()->plugin(pn->name());
      if (_plugin) {
        const QValueList<Plugin::Data::IOValue>& itable = _plugin->data()._inputs;
        const QValueList<Plugin::Data::IOValue>& otable = _plugin->data()._outputs;
        Plugin::countScalarsVectorsAndStrings(itable, _inputScalarCnt, _inputVectorCnt, _inputStringCnt, _inPid);
        unsigned ignore;
        Plugin::countScalarsVectorsAndStrings(otable, _outputScalarCnt, _outputVectorCnt, _outputStringCnt, ignore);
        assert(_inputStringCnt == 0 && _outputStringCnt == 0); // FIXME: implement support for strings
        _inScalars = new double[_inputScalarCnt];
        _outScalars = new double[_outputScalarCnt];
        _inVectors = new double*[_inputVectorCnt];
        _outVectors = new double*[_outputVectorCnt];
        _inArrayLens = new int[_inputVectorCnt];
        _outArrayLens = new int[_outputVectorCnt];
        memset(_outVectors, 0, _outputVectorCnt*sizeof(double*));
        memset(_outArrayLens, 0, _outputVectorCnt*sizeof(int));
      } else {
        KstDebug::self()->log(i18n("Equation was unable to load plugin %1.").arg(pn->name()), KstDebug::Warning);
      }
    } else {
      KstDebug::self()->log(i18n("A plugin call in an equation requires the first argument to be the name of the plugin."), KstDebug::Warning);
    }
  } else {
    for (int i = 0; FTable[i].name; ++i) {
      if (strcasecmp(FTable[i].name, name) == 0) {
        _f = (void*)FTable[i].func;
        break;
      }
    }
  }
}


Function::~Function() {
  free(_name);
  _name = 0L;
  delete _args;
  _args = 0L;
  _f = 0L;
  if (_localData) {
    if (!_plugin->freeLocalData(&_localData)) {
      free(_localData);
    }
    _localData = 0L;
  }
  _plugin = 0L;
  delete[] _inScalars;
  delete[] _inVectors;
  delete[] _outScalars;
  for (uint i = 0; i < _outputVectorCnt; ++i) {
    delete[] _outVectors[i];
  }
  delete[] _outVectors;
  delete[] _inArrayLens;
  delete[] _outArrayLens;
}


KstObject::UpdateType Function::update(int counter, Context *ctx) {
  KstObject::UpdateType ut = _args->update(counter, ctx);
  if (ut == KstObject::NO_CHANGE && counter != -1) {
    return KstObject::NO_CHANGE;
  }

  if (!_plugin) {
    return KstObject::NO_CHANGE;
  }

  const QValueList<Plugin::Data::IOValue>& itable = _plugin->data()._inputs;
  uint itcnt = 0, vitcnt = 0, cnt = 0;
  // Populate the input scalars and vectors
  for (QValueList<Plugin::Data::IOValue>::ConstIterator it = itable.begin(); it != itable.end(); ++it) {
    if ((*it)._type == Plugin::Data::IOValue::TableType) {
      Data *d = dynamic_cast<Data*>(_args->node(cnt + 1));
      if (d && d->_vector) {
        _inVectors[vitcnt] = d->_vector->value();
        _inArrayLens[vitcnt++] = d->_vector->length();
      } else {
        Identifier *pn = dynamic_cast<Identifier*>(_args->node(cnt + 1));
        if (pn && 0 == strcmp(pn->name(), "x")) {
          if (!ctx->xVector) {
            _outputIndex = -424242;
            // Hope we recover later
            return KstObject::NO_CHANGE;
          }
          _inVectors[vitcnt] = ctx->xVector->value();
          _inArrayLens[vitcnt++] = ctx->xVector->length();
        } else {
          _outputIndex = -424242;
          KstDebug::self()->log(i18n("Plugin %2 failed when called from equation.  Argument %1 was not found.").arg(cnt + 1).arg(_plugin->data()._name), KstDebug::Warning);
          return KstObject::NO_CHANGE;
        }
      }
      ++cnt;
    } else if ((*it)._type == Plugin::Data::IOValue::FloatType) {
      Node *n = _args->node(cnt + 1);
      _inScalars[itcnt++] = n->value(ctx);
      ++cnt;
    } else if ((*it)._type == Plugin::Data::IOValue::PidType) {
      _inScalars[itcnt++] = getpid();
    }
  }

  int rc;
  if (_plugin->data()._localdata) {
    rc = _plugin->call(_inVectors, _inArrayLens, _inScalars, _outVectors, _outArrayLens, _outScalars, &_localData);
  } else {
    rc = _plugin->call(_inVectors, _inArrayLens, _inScalars, _outVectors, _outArrayLens, _outScalars);
  }

  _outputIndex = -424242;
  if (rc != 0) {
    KstDebug::self()->log(i18n("Plugin %1 failed when called from equation.").arg(_plugin->data()._name), KstDebug::Warning);
    return KstObject::NO_CHANGE;
  }

  if (!_plugin->data()._filterOutputVector.isEmpty()) {
    int loc = 0;
    bool found = false;
    const QValueList<Plugin::Data::IOValue>& otable = _plugin->data()._outputs;
    for (QValueList<Plugin::Data::IOValue>::ConstIterator it = otable.begin(); it != otable.end(); ++it) {
      if ((*it)._type == Plugin::Data::IOValue::TableType) {
        if ((*it)._name == _plugin->data()._filterOutputVector) {
          found = true;
          break;
        }
        loc++;
      }
    }
    if (found) {
      _outputIndex = loc;
    }
  }

  if (_outputIndex == -424242) {
    if (_outputVectorCnt > 0) {
      if (_outVectors[0] && _outArrayLens[0] > 1) {
        _outputIndex = 0;
      }
    } else if (_outputScalarCnt > 0 && _outScalars) { // make sense?
      _outputIndex = -1;
    }
  }

  return KstObject::UPDATE;
}


double Function::evaluatePlugin(Context *ctx) {
  if (_outputIndex >= 0) {
    return ::interpolate(_outVectors[_outputIndex], _outArrayLens[_outputIndex], ctx->i, ctx->sampleCount);
  } else if (_outputIndex == -424242) {
    return ctx->noPoint;
  } else { // make sense?
    return _outScalars[abs(_outputIndex) - 1];
  }
  
  return ctx->noPoint;
}


double Function::value(Context *ctx) {
  if (_plugin) {
    return evaluatePlugin(ctx);
  }

  if (!_f) {
    return ctx->noPoint;
  }

  // FIXME: support multiple arguments
  double x = _args->at(0, ctx);
  return ((double (*)(double))_f)(x);
}


bool Function::isConst() {
  return _args->isConst();
}


bool Function::isPlugin() const {
  return _plugin != 0L;
}


void Function::collectVectors(KstVectorList& c) {
  _args->collectVectors(c);
}


/////////////////////////////////////////////////////////////////
ArgumentList::ArgumentList()
: Node() {
  //printf("%p: New Argument List\n", (void*)this);
  _args.setAutoDelete(true);
}


ArgumentList::~ArgumentList() {
}


void ArgumentList::appendArgument(Node *arg) {
  _args.append(arg);
}


double ArgumentList::at(int arg, Context *ctx) {
  Node *n = _args.at(arg);
  if (n) {
    return n->value(ctx);
  }
  return ctx->noPoint;
}


bool ArgumentList::isConst() {
  for (Node *i = _args.first(); i; i = _args.next()) {
    if (!i->isConst()) {
      return false;
    }
  }
  return true;
}


void ArgumentList::collectVectors(KstVectorList& c) {
  for (Node *i = _args.first(); i; i = _args.next()) {
    i->collectVectors(c);
  }
}


Node *ArgumentList::node(int idx) {
  return _args.at(idx);
}


KstObject::UpdateType ArgumentList::update(int counter, Context *ctx) {
  bool updated = false;
  for (Node *i = _args.first(); i; i = _args.next()) {
    updated = updated || KstObject::UPDATE == i->update(counter, ctx);
  }
  return updated ? KstObject::UPDATE : KstObject::NO_CHANGE;
}


/////////////////////////////////////////////////////////////////
static struct {
  const char *name;
  double value;
} ITable[] = {
  {"e", 2.7128182846},
  {"pi", 3.1415926536},
  {0, 0.0}
};

Identifier::Identifier(char *name)
: Node(), _name(name), _const(0L) {
  //printf("%p: New Identifier: %s\n", (void*)this, name);
  for (int i = 0; ITable[i].name; ++i) {
    if (strcasecmp(ITable[i].name, name) == 0) {
      _const = &ITable[i].value;
      break;
    }
  }
}


Identifier::~Identifier() {
  free(_name);
  _name = 0L;
}


const char *Identifier::name() const {
  return _name;
}


double Identifier::value(Context *ctx) {
  if (_const) {
    return *_const;
  } else if (_name[0] == 'x' && _name[1] == 0) {
    return ctx->x;
  } else {
    return ctx->noPoint;
  }
}


bool Identifier::isConst() {
  return _const != 0L || !(_name[0] == 'x' && _name[1] == 0);
}


/////////////////////////////////////////////////////////////////
Data::Data(char *name)
: Node(), _name(name) {
  //printf("%p: New Data Object: %s\n", (void*)this, name);
  QString n = QString(_name).mid(1);
  n.truncate(n.length() - 1);
  _vector = *KST::vectorList.findTag(n);
  if (!_vector) {
    _scalar = *KST::scalarList.findTag(n);
  }
}


Data::~Data() {
  free(_name);
  _name = 0L;
}


double Data::value(Context *ctx) {
  if (_vector) {
    return _vector->interpolate(ctx->i, ctx->sampleCount);
  } else if (_scalar) {
    return _scalar->value();
  } else {
    return ctx->noPoint;
  }
}


bool Data::isConst() {
  return false;
}


void Data::collectVectors(KstVectorList& c) {
  if (_vector && !c.contains(_vector)) {
    c.append(_vector);
  }
}


KstObject::UpdateType Data::update(int counter, Context *ctx) {
  Q_UNUSED(ctx)
  if (_vector) {
    return _vector->update(counter);
  } else if (_scalar) {
    return _scalar->update(counter);
  }
  return KstObject::NO_CHANGE;
}


/////////////////////////////////////////////////////////////////
Number::Number(double n)
: Node(), _n(n) {
  //printf("%p: New Number: %lf\n", (void*)this, n);
}


Number::~Number() {
}


double Number::value(Context*) {
  return _n;
}


bool Number::isConst() {
  return true;
}


/////////////////////////////////////////////////////////////////
LogicalNot::LogicalNot(Node *node)
: Node(), _n(node) {
  //printf("%p: New LogicalNot: %p\n", (void*)this, (void*)n);
}


LogicalNot::~LogicalNot() {
}


double LogicalNot::value(Context *ctx) {
  double v = _n->value(ctx);
  return (v == v) ? (v == 0.0) : 1.0;
}


bool LogicalNot::isConst() {
  return _n->isConst();
}


/////////////////////////////////////////////////////////////////
BitwiseAnd::BitwiseAnd(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New And: %p & %p\n", (void*)this, (void*)left, (void*)right);
}


BitwiseAnd::~BitwiseAnd() {
}


double BitwiseAnd::value(Context *ctx) {
  return long(_left->value(ctx)) & long(_right->value(ctx));
}


bool BitwiseAnd::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
BitwiseOr::BitwiseOr(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Or: %p | %p\n", (void*)this, (void*)left, (void*)right);
}


BitwiseOr::~BitwiseOr() {
}


double BitwiseOr::value(Context *ctx) {
  return long(_left->value(ctx)) | long(_right->value(ctx));
}


bool BitwiseOr::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
LogicalAnd::LogicalAnd(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New And: %p && %p\n", (void*)this, (void*)left, (void*)right);
}


LogicalAnd::~LogicalAnd() {
}


double LogicalAnd::value(Context *ctx) {
  return (_left->value(ctx) && _right->value(ctx)) ? EQ_TRUE : EQ_FALSE;
}


bool LogicalAnd::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
LogicalOr::LogicalOr(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New Or: %p || %p\n", (void*)this, (void*)left, (void*)right);
}


LogicalOr::~LogicalOr() {
}


double LogicalOr::value(Context *ctx) {
  return (_left->value(ctx) || _right->value(ctx)) ? EQ_TRUE : EQ_FALSE;
}


bool LogicalOr::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
LessThan::LessThan(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New LessThan: %p < %p\n", (void*)this, (void*)left, (void*)right);
}


LessThan::~LessThan() {
}


double LessThan::value(Context *ctx) {
  return _left->value(ctx) < _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool LessThan::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
LessThanEqual::LessThanEqual(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New LessThanEqual: %p <= %p\n", (void*)this, (void*)left, (void*)right);
}


LessThanEqual::~LessThanEqual() {
}


double LessThanEqual::value(Context *ctx) {
  return _left->value(ctx) <= _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool LessThanEqual::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
GreaterThan::GreaterThan(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New GreaterThan: %p > %p\n", (void*)this, (void*)left, (void*)right);
}


GreaterThan::~GreaterThan() {
}


double GreaterThan::value(Context *ctx) {
  return _left->value(ctx) > _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool GreaterThan::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
GreaterThanEqual::GreaterThanEqual(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New GreaterThanEqual: %p >= %p\n", (void*)this, (void*)left, (void*)right);
}


GreaterThanEqual::~GreaterThanEqual() {
}


double GreaterThanEqual::value(Context *ctx) {
  return _left->value(ctx) >= _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool GreaterThanEqual::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
EqualTo::EqualTo(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New EqualTo: %p == %p\n", (void*)this, (void*)left, (void*)right);
}


EqualTo::~EqualTo() {
}


double EqualTo::value(Context *ctx) {
  return _left->value(ctx) == _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool EqualTo::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////
NotEqualTo::NotEqualTo(Node *left, Node *right)
: BinaryNode(left, right) {
  //printf("%p: New NotEqualTo: %p != %p\n", (void*)this, (void*)left, (void*)right);
}


NotEqualTo::~NotEqualTo() {
}


double NotEqualTo::value(Context *ctx) {
  return _left->value(ctx) != _right->value(ctx) ? EQ_TRUE : EQ_FALSE;
}


bool NotEqualTo::isConst() {
  return _left->isConst() && _right->isConst();
}


/////////////////////////////////////////////////////////////////

NodeVisitor::NodeVisitor() {
}


NodeVisitor::~NodeVisitor() {
}


void NodeVisitor::visitNode(Node*) {
}


void NodeVisitor::visitBinaryNode(BinaryNode*) {
}

/////////////////////////////////////////////////////////////////

FoldVisitor::FoldVisitor(Context* ctxIn, Node* rootNode) : NodeVisitor(), _ctx(ctxIn) {
  rootNode->visit(this);
  _ctx = 0L; // avoids context being marked as 'still reachable'
}


FoldVisitor::~FoldVisitor() {
}


void FoldVisitor::visitNode(Node*) {
}


void FoldVisitor::visitBinaryNode(BinaryNode * n) {
  if (n->left()->isConst() && dynamic_cast<Number*>(n->left()) == 0L) {
    double v = n->left()->value(_ctx);
    delete n->left();
    n->left() = new Number(v);
  } else {
    n->left()->visit(this);
  }

  if (n->right()->isConst() && dynamic_cast<Number*>(n->right()) == 0L) {
    double v = n->right()->value(_ctx);
    delete n->right();
    n->right() = new Number(v);
  } else {
    n->right()->visit(this);
  }
}


static double interpolate(double *_v, int _size, int in_i, int ns_i) {
  double fj, fdj;
  int j;

  /** Limits checks - optional? **/
  if (in_i < 0) {
    return _v[0];
  }

  if (in_i >= ns_i - 1) {
    return _v[_size - 1];
  }

  /** speedup check **/
  if (ns_i == _size) { /* no extrapolating or decimating needed */
    return _v[in_i];
  }

  fj = in_i * double(_size - 1) / double(ns_i-1); //scaled index

  j = int(floor(fj));        // index of sample one lower

  // This is optimized to avoid unnecessary isnan calls!
  if (_v[j + 1] != _v[j + 1]) {
    if (KST_ISNAN(_v[j + 1])) {
      return KST::NOPOINT;
    }
  }
  if (_v[j] != _v[j]) {
    if (KST_ISNAN(_v[j])) {
      return KST::NOPOINT;
    }
  }

  fdj = fj - float(j);

  return _v[j + 1] * fdj + _v[j] * (1.0 - fdj);
}


// vim: ts=2 sw=2 et
