import {reduce} from './reduce';
import {map} from './map';
import {concat} from './concat';
import {hasMethod} from '../object/hasMethod';
import {curry} from '../functional/curry';
import {isTransducer} from '../type/isTransducer';
import {Base} from '../transducer/Base';

class Chainer extends Base{
  constructor(fn, xf){
    super();
    this.xf = xf;
    this.f = fn;
  }
  '@@transducer/step'(result, input) {
    return reduce(
      this.xf['@@transducer/step'].bind(this.xf),
      result,
      this.f(input)
    );
  }
}

const _chain =
  (fn, xf) => concat(...map(fn, xf))


export const chain =
  curry(
    (fn, xf) =>
        hasMethod('chain', xf) ? xf.chain(fn) :
        isTransducer(xf)       ? new Chainer(fn, xf)
                               : _chain(fn, xf)
  );

import {reduce} from './reduce';
import {isArrayLike} from '../type/isArrayLike';
import {into} from './into';
import {hasMethod} from '../object/hasMethod';
import {curry} from '../functional/curry';
import {isTransducer} from '../type/isTransducer';
import {Base} from '../transducer/Base';

class Chainer extends Base {
  constructor(fn, xf){
    super();
    this.xf = xf;
    this.f = fn;
  }
  valueStep(result, value){
    return isArrayLike(value) ? this.arrayStep(result, value)
                              : this.elementStep(result, value) ;
  }
  arrayStep(result,value) {
    return reduce(
      this.xf['@@transducer/step'].bind(this.xf),
      result,
      value
    );
  }
  elementStep(result, value){
    return this.xf['@@transducer/step'](result,value);
  }
  '@@transducer/step'(result, input) {
    return this.valueStep(result, this.f(input));
  }
}

export const chain =
  curry(
    (fn, xf) =>
        hasMethod('chain', xf) ? xf.chain(fn) :
        isTransducer(xf)       ? new Chainer(fn, xf)
                               : into([], chain(fn), xf)
  );

use only transducers or custom chain