/* globals suite test */

var assert = require('assert') var flat = require('../index') var flatten = flat.flatten var unflatten = flat.unflatten

var primitives = {

String: 'good morning',
Number: 1234.99,
Boolean: true,
Date: new Date(),
null: null,
undefined: undefined

}

suite('Flatten Primitives', function () {

Object.keys(primitives).forEach(function (key) {
  var value = primitives[key]

  test(key, function () {
    assert.deepEqual(flatten({
      hello: {
        world: value
      }
    }), {
      'hello.world': value
    })
  })
})

})

suite('Unflatten Primitives', function () {

Object.keys(primitives).forEach(function (key) {
  var value = primitives[key]

  test(key, function () {
    assert.deepEqual(unflatten({
      'hello.world': value
    }), {
      hello: {
        world: value
      }
    })
  })
})

})

suite('Flatten', function () {

test('Nested once', function () {
  assert.deepEqual(flatten({
    hello: {
      world: 'good morning'
    }
  }), {
    'hello.world': 'good morning'
  })
})

test('Nested twice', function () {
  assert.deepEqual(flatten({
    hello: {
      world: {
        again: 'good morning'
      }
    }
  }), {
    'hello.world.again': 'good morning'
  })
})

test('Multiple Keys', function () {
  assert.deepEqual(flatten({
    hello: {
      lorem: {
        ipsum: 'again',
        dolor: 'sit'
      }
    },
    world: {
      lorem: {
        ipsum: 'again',
        dolor: 'sit'
      }
    }
  }), {
    'hello.lorem.ipsum': 'again',
    'hello.lorem.dolor': 'sit',
    'world.lorem.ipsum': 'again',
    'world.lorem.dolor': 'sit'
  })
})

test('Custom Delimiter', function () {
  assert.deepEqual(flatten({
    hello: {
      world: {
        again: 'good morning'
      }
    }
  }, {
    delimiter: ':'
  }), {
    'hello:world:again': 'good morning'
  })
})

test('Empty Objects', function () {
  assert.deepEqual(flatten({
    hello: {
      empty: {
        nested: { }
      }
    }
  }), {
    'hello.empty.nested': { }
  })
})

if (typeof Buffer !== 'undefined') {
  test('Buffer', function () {
    assert.deepEqual(flatten({
      hello: {
        empty: {
          nested: Buffer.from('test')
        }
      }
    }), {
      'hello.empty.nested': Buffer.from('test')
    })
  })
}

if (typeof Uint8Array !== 'undefined') {
  test('typed arrays', function () {
    assert.deepEqual(flatten({
      hello: {
        empty: {
          nested: new Uint8Array([1, 2, 3, 4])
        }
      }
    }), {
      'hello.empty.nested': new Uint8Array([1, 2, 3, 4])
    })
  })
}

test('Custom Depth', function () {
  assert.deepEqual(flatten({
    hello: {
      world: {
        again: 'good morning'
      }
    },
    lorem: {
      ipsum: {
        dolor: 'good evening'
      }
    }
  }, {
    maxDepth: 2
  }), {
    'hello.world': {
      again: 'good morning'
    },
    'lorem.ipsum': {
      dolor: 'good evening'
    }
  })
})

test('Should keep number in the left when object', function () {
  assert.deepEqual(flatten({
    hello: {
      '0200': 'world',
      '0500': 'darkness my old friend'
    }
  }), {
    'hello.0200': 'world',
    'hello.0500': 'darkness my old friend'
  })
})

})

suite('Unflatten', function () {

test('Nested once', function () {
  assert.deepEqual({
    hello: {
      world: 'good morning'
    }
  }, unflatten({
    'hello.world': 'good morning'
  }))
})

test('Nested twice', function () {
  assert.deepEqual({
    hello: {
      world: {
        again: 'good morning'
      }
    }
  }, unflatten({
    'hello.world.again': 'good morning'
  }))
})

test('Multiple Keys', function () {
  assert.deepEqual({
    hello: {
      lorem: {
        ipsum: 'again',
        dolor: 'sit'
      }
    },
    world: {
      greet: 'hello',
      lorem: {
        ipsum: 'again',
        dolor: 'sit'
      }
    }
  }, unflatten({
    'hello.lorem.ipsum': 'again',
    'hello.lorem.dolor': 'sit',
    'world.lorem.ipsum': 'again',
    'world.lorem.dolor': 'sit',
    'world': {greet: 'hello'}
  }))
})

test('nested objects do not clobber each other when a.b inserted before a', function () {
  var x = {}
  x['foo.bar'] = {t: 123}
  x['foo'] = {p: 333}
  assert.deepEqual(unflatten(x), {
    foo: {
      bar: {
        t: 123
      },
      p: 333
    }
  })
})

test('Custom Delimiter', function () {
  assert.deepEqual({
    hello: {
      world: {
        again: 'good morning'
      }
    }
  }, unflatten({
    'hello world again': 'good morning'
  }, {
    delimiter: ' '
  }))
})

test('Overwrite', function () {
  assert.deepEqual({
    travis: {
      build: {
        dir: '/home/travis/build/kvz/environmental'
      }
    }
  }, unflatten({
    travis: 'true',
    travis_build_dir: '/home/travis/build/kvz/environmental'
  }, {
    delimiter: '_',
    overwrite: true
  }))
})

test('Messy', function () {
  assert.deepEqual({
    hello: { world: 'again' },
    lorem: { ipsum: 'another' },
    good: {
      morning: {
        hash: {
          key: { nested: {
            deep: { and: { even: {
              deeper: { still: 'hello' }
            } } }
          } }
        },
        again: { testing: { 'this': 'out' } }
      }
    }
  }, unflatten({
    'hello.world': 'again',
    'lorem.ipsum': 'another',
    'good.morning': {
      'hash.key': {
        'nested.deep': {
          'and.even.deeper.still': 'hello'
        }
      }
    },
    'good.morning.again': {
      'testing.this': 'out'
    }
  }))
})

suite('Overwrite + non-object values in key positions', function () {
  test('non-object keys + overwrite should be overwritten', function () {
    assert.deepEqual(flat.unflatten({ a: null, 'a.b': 'c' }, {overwrite: true}), { a: { b: 'c' } })
    assert.deepEqual(flat.unflatten({ a: 0, 'a.b': 'c' }, {overwrite: true}), { a: { b: 'c' } })
    assert.deepEqual(flat.unflatten({ a: 1, 'a.b': 'c' }, {overwrite: true}), { a: { b: 'c' } })
    assert.deepEqual(flat.unflatten({ a: '', 'a.b': 'c' }, {overwrite: true}), { a: { b: 'c' } })
  })

  test('overwrite value should not affect undefined keys', function () {
    assert.deepEqual(flat.unflatten({ a: undefined, 'a.b': 'c' }, {overwrite: true}), { a: { b: 'c' } })
    assert.deepEqual(flat.unflatten({ a: undefined, 'a.b': 'c' }, {overwrite: false}), { a: { b: 'c' } })
  })

  test('if no overwrite, should ignore nested values under non-object key', function () {
    assert.deepEqual(flat.unflatten({ a: null, 'a.b': 'c' }), { a: null })
    assert.deepEqual(flat.unflatten({ a: 0, 'a.b': 'c' }), { a: 0 })
    assert.deepEqual(flat.unflatten({ a: 1, 'a.b': 'c' }), { a: 1 })
    assert.deepEqual(flat.unflatten({ a: '', 'a.b': 'c' }), { a: '' })
  })
})

suite('.safe', function () {
  test('Should protect arrays when true', function () {
    assert.deepEqual(flatten({
      hello: [
          { world: { again: 'foo' } },
         { lorem: 'ipsum' }
      ],
      another: {
        nested: [{ array: { too: 'deep' } }]
      },
      lorem: {
        ipsum: 'whoop'
      }
    }, {
      safe: true
    }), {
      hello: [
          { world: { again: 'foo' } },
         { lorem: 'ipsum' }
      ],
      'lorem.ipsum': 'whoop',
      'another.nested': [{ array: { too: 'deep' } }]
    })
  })

  test('Should not protect arrays when false', function () {
    assert.deepEqual(flatten({
      hello: [
          { world: { again: 'foo' } },
         { lorem: 'ipsum' }
      ]
    }, {
      safe: false
    }), {
      'hello.0.world.again': 'foo',
      'hello.1.lorem': 'ipsum'
    })
  })
})

suite('.object', function () {
  test('Should create object instead of array when true', function () {
    var unflattened = unflatten({
      'hello.you.0': 'ipsum',
      'hello.you.1': 'lorem',
      'hello.other.world': 'foo'
    }, {
      object: true
    })
    assert.deepEqual({
      hello: {
        you: {
          0: 'ipsum',
          1: 'lorem'
        },
        other: { world: 'foo' }
      }
    }, unflattened)
    assert(!Array.isArray(unflattened.hello.you))
  })

  test('Should create object instead of array when nested', function () {
    var unflattened = unflatten({
      'hello': {
        'you.0': 'ipsum',
        'you.1': 'lorem',
        'other.world': 'foo'
      }
    }, {
      object: true
    })
    assert.deepEqual({
      hello: {
        you: {
          0: 'ipsum',
          1: 'lorem'
        },
        other: { world: 'foo' }
      }
    }, unflattened)
    assert(!Array.isArray(unflattened.hello.you))
  })

  test('Should keep the zero in the left when object is true', function () {
    var unflattened = unflatten({
      'hello.0200': 'world',
      'hello.0500': 'darkness my old friend'
    }, {
      object: true
    })

    assert.deepEqual({
      hello: {
        '0200': 'world',
        '0500': 'darkness my old friend'
      }
    }, unflattened)
  })

  test('Should not create object when false', function () {
    var unflattened = unflatten({
      'hello.you.0': 'ipsum',
      'hello.you.1': 'lorem',
      'hello.other.world': 'foo'
    }, {
      object: false
    })
    assert.deepEqual({
      hello: {
        you: ['ipsum', 'lorem'],
        other: { world: 'foo' }
      }
    }, unflattened)
    assert(Array.isArray(unflattened.hello.you))
  })
})

if (typeof Buffer !== 'undefined') {
  test('Buffer', function () {
    assert.deepEqual(unflatten({
      'hello.empty.nested': Buffer.from('test')
    }), {
      hello: {
        empty: {
          nested: Buffer.from('test')
        }
      }
    })
  })
}

if (typeof Uint8Array !== 'undefined') {
  test('typed arrays', function () {
    assert.deepEqual(unflatten({
      'hello.empty.nested': new Uint8Array([1, 2, 3, 4])
    }), {
      hello: {
        empty: {
          nested: new Uint8Array([1, 2, 3, 4])
        }
      }
    })
  })
}

})

suite('Arrays', function () {

test('Should be able to flatten arrays properly', function () {
  assert.deepEqual({
    'a.0': 'foo',
    'a.1': 'bar'
  }, flatten({
    a: ['foo', 'bar']
  }))
})

test('Should be able to revert and reverse array serialization via unflatten', function () {
  assert.deepEqual({
    a: ['foo', 'bar']
  }, unflatten({
    'a.0': 'foo',
    'a.1': 'bar'
  }))
})

test('Array typed objects should be restored by unflatten', function () {
  assert.equal(
      Object.prototype.toString.call(['foo', 'bar'])
    , Object.prototype.toString.call(unflatten({
      'a.0': 'foo',
      'a.1': 'bar'
    }).a)
  )
})

test('Do not include keys with numbers inside them', function () {
  assert.deepEqual(unflatten({
    '1key.2_key': 'ok'
  }), {
    '1key': {
      '2_key': 'ok'
    }
  })
})

})