const Const = require("./Const")
const Identity = require("./Identity")
const pipe = (...arr) => arr.reduce(
    (acc, next) => x => next(acc(x))
  , x => x
)
const compose = (...arr) => pipe(...arr.reverse());
const lens = (getter, setter) => toFunctorFn =>
    target => {
        
        const functor = toFunctorFn(getter(target))
        
        return functor.map(focus => setter(focus, target))
    };
const view = (lens, x) => lens(Const.pure)(x).run()
  
const over = (lens, f) => x => lens(y => Identity.pure(f(y)))(x).run()
const set = (lens, v) => over(lens, () => v)
const prop = name => lens(x => x[name], (a, x) => ({...x, [name]: a}))
const xLens = lens(x => x.x, (a, s) => {
    return {...s, x: a}
});
const yLens = prop('y')
console.log(
    'view', view(xLens, {x: 12, y: 32})
)
console.log(
    'over', over(xLens, x => x * 2)({x: 12, y: 32})    
)
console.log(
    'set', set(xLens, 42)({x: 12, y: 32})    
)
console.log(
    'set', set(yLens, 42)({x: 12, y: 32})    
)
console.log(
    'set', set(compose(prop('x'), prop('y')), 42)({x: {y: 71, z: 61}, w: 81})    
)
console.log(
    pipe(
        set(prop('x'), 87)
      , over(prop('y'), y => y + 3)
      , over(prop('x'), x => x / 3)
    )({x: 7, y: 1})    
)
console.log(
    'view', view(compose(prop('x'), prop('y')),{x: {y: 71, z: 61}, w: 81})    
)
class Const {
  constructor(z) {
    this.run = () => z
    
    // this :: Const z x
    // (x -> y) -> Const z y
    this.map = f => new Const(this.run())
    
  }
}
Const.pure = z => new Const(z)
module.exports = Const
class Identity {
  constructor(x) {
    this.run = () => x
    
    // this :: Identity x
    // (x -> y) -> Identity y
    this.map = f => new Identity(f(this.run()))
    
    // this :: Identity x
    // (x -> Identity y) -> Identity y
    this.bind = f => f(this.run())
    
    // this :: Identity x
    // (x -> (y || Identity y)) -> Identity y
    this.then = f => {
      const y = f(this.run())
      return y instanceof Identity ? y : new Identity(y)
    }
  }
}
Identity.pure = x => new Identity(x)
module.exports = Identity