'use strict'

let gen = {
  memo: new Map(),
  graph:null,
  accum:0,
  
  isNotNumber( v ) {
    return (typeof v)[0] !== 'n'  
  },
  
  getUID() {
    return this.accum++
  },
  
  out() {
    this.memo.clear()
    return this.graph.out()
  },
}

let table = frequency => {
  var tableSize = 1024,
      table_ = new Float32Array( tableSize ),
      phase = 0,
      tableFreq = 44100 / tableSize
  
  for( var i = 0; i < table_.length; i++ ) {
    table_[ i ] = Math.sin( ( i / tableSize ) * ( Math.PI * 2 ) )
  }
  
  return { 
    uid: gen.getUID(),
/*    phase: 0,
    frequency,
    table,
    tableFreq: 44100 / tableSize, // replace with sample rate
*/
    memo: gen.memo,
    isNotNumber: gen.isNotNumber,
    out:  function( freq ) {
      var index, frac, index2, val1, val2, sign
            
      phase += freq / tableFreq
      //while( this.phase >= 1024 ) this.phase -= 1024
    
      index   = phase | 0
      frac    = phase - index
      index   = index & 1023
      index2  = index === 1023 ? 0 : index + 1
      val1    = table_[ index ]
      val2    = table_[ index2 ]
    
      return val1 + ( frac * (val2 - val1) )
    }
  }
}

let wavetable = function() {
  var phase = 0,
      tableSize = 1024,
      tableFreq = 44100 / 1024,
      table = new Float32Array( tableSize )
  
  for( var i = 0; i < table.length; i++ ) {
    table[ i ] = Math.sin( ( i / tableSize ) * ( Math.PI * 2 ) )
  }

  this.callback = function(frequency, amp, sync) { 
    var index, frac, index2, val1, val2, sign;
            
    phase += frequency / tableFreq;
    while(phase >= 1024) phase -= 1024;  
    
    index   = phase | 0;
    frac    = phase - index;
    index   = index & 1023;
    index2  = index === 1023 ? 0 : index + 1;
    val1    = table[index];
    val2    = table[index2];
    
    return ( val1 + ( frac * (val2 - val1) ) ) * amp;
  }
}

let tableClosure = frequency => {
  let phase = 0, tableSize = 1024, tableFreq = 44100 / tableSize, table = new Float32Array( tableSize )
  let ugen = function() {
    var index, frac, index2, val1, val2, sign
            
    phase += frequency / tableFreq
    //while( this.phase >= 1024 ) this.phase -= 1024
    
    index   = phase | 0
    frac    = phase - index
    index   = index & 1023
    index2  = index === 1023 ? 0 : index + 1
    val1    = table[ index ]
    val2    = table[ index2 ]
    
    return ( val1 + ( frac * (val2 - val1) ) )
  }
  
  for( let i = 0; i < table.length; i++ ) {
    table[ i ] = Math.sin( ( i / tableSize ) * ( Math.PI * 2 ) )
  }
  
  return ugen.bind({ 
    uid: gen.getUID(),
    frequency,
    phase: 0,
    table,
    tableFreq: 44100 / tableSize, // replace with sample rate
    memo: gen.memo,
    isNotNumber: gen.isNotNumber
  })
}

let abs = x => {
  let ugen = function() {
    if( this.uid in this.memo ) return this.memo[ this.uid ]
    
    let out = this.inputs[ 0 ]
    
    if( this.isNotNumber( out ) ) out = out()
    
    out = this.abs( out )
      
    this.memo.set( this.uid, out )
      
    return out
  }
  
  return {
    uid:          gen.getUID(),
    inputs:       [ x ],
    abs:          Math.abs,
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber,
    out:ugen
  }
}

let sin = x => {
  let ugen = function() {
    if( this.uid in this.memo ) return this.memo[ this.uid ]
    
    let out = this.inputs[ 0 ]
    
    if( this.isNotNumber( out ) ) out = out()
    
    out = this.sin( out )
      
    this.memo.set( this.uid, out )
      
    return out
  }
  
  return ugen.bind({
    uid:          gen.getUID(),
    inputs:       [ x ],
    sin:          Math.sin,
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber
  })
}

let mul = ( x,y ) => {
  let ugen = function() {
    //if( this.uid in this.memo ) return this.memo[ this.uid ]
    let x = this.x, y = this.y
    
    if( xIsUgen ) x = x.out()
    if( yIsUgen ) y = y.out()  
    
    let out = x * y
      
    //this.memo.set( this.uid, out )
      
    return out
  }
  
  let xIsUgen = gen.isNotNumber( x ),
      yIsUgen = gen.isNotNumber( y )
  
  return {
    uid:          gen.getUID(),
    x,y,xIsUgen,yIsUgen,
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber,
    out:ugen
  }
}

let add = ( x,y ) => {
  let ugen = function() {
    if( this.uid in this.memo ) return this.memo[ this.uid ]
    
    let x = this.inputs[ 0 ], y = this.inputs[ 1 ]
    
    if( this.isNotNumber( x ) ) x = x()
    if( this.isNotNumber( y ) ) y = y()      
    
    let out = x + y
      
    this.memo.set( this.uid, out )
      
    return out
  }
  
  return ugen.bind({
    uid:          gen.getUID(),
    inputs:       [ x,y ],
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber
  })
}

let accum = ( incr, reset=0, min=0, max=1 ) => {
  let ugen = function() {
    if( this.uid in this.memo ) return this.memo[ this.uid ]
    
    let incr = this.inputs[ 0 ], reset = this.inputs[ 1 ]
    
    if( this.isNotNumber( incr ) ) incr = incr()
    if( this.isNotNumber( reset ) ) reset = reset()      
    
    this.out += incr
      
    if( reset ) {
      this.out = this.min
    }else{
      if( this.out > this.max ) this.out = this.min
    }
    
    this.memo.set( this.uid, this.out )
      
    return this.out
  }
  
  return ugen.bind({
    min, 
    max,
    out:          0,
    uid:          gen.getUID(),
    inputs:       [ incr, reset ],
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber
  })
}

let bus = ( ...args ) => {
  let ugen = function() {
    //if( this.uid in this.memo ) return this.memo[ this.uid ]
    let out = 0
    
    for( let i = 0, l = this.inputs.length; i < l; i++ ) {
      let input = this.inputs[ i ]
      out += input.value.out()//input.isNotNumber ? input.value.out() : input.value
    }
    
    //this.memo.set( this.uid, out )
    
    return out
  }
  
  let inputs = args.map( ( v ) => ({
    isNotNumber: gen.isNotNumber( v ),
    value: v
  }) )
  
  return {
    uid:          gen.getUID(),
    inputs,
    memo:         gen.memo,
    isNotNumber:  gen.isNotNumber,
    out:ugen
  }
}

let param = value => {
  let p = function( v ) {
    if( v !== undefined ) this.value = v
    return this.value
  }
  
  return p.bind({ value })
}

let PI = Math.PI,
    SR = 44100

module.exports = { gen, add, mul, accum, abs, sin, bus, param, table, PI, SR, tableClosure, wavetable }