/* global describe, it */
'use strict'
const assert = require('chai').assert
const julian = require('../lib/julian')
const lune = require('../lib/lune')

describe('lune', function () {
  const observations = [
    ['1989-01-07T19:22Z', 0.00, lune.PHASE_NEW],
    ['1989-01-14T13:58Z', 0.25, lune.PHASE_FIRST],
    ['1989-01-21T21:33Z', 0.50, lune.PHASE_FULL],
    ['1989-01-30T02:02Z', 0.75, lune.PHASE_LAST],
    ['1989-02-06T07:37Z', 0.00, lune.PHASE_NEW],
    ['1989-02-12T23:15Z', 0.25, lune.PHASE_FIRST],
    ['1989-02-20T15:32Z', 0.50, lune.PHASE_FULL],
    ['1989-02-28T20:08Z', 0.75, lune.PHASE_LAST],
    ['1989-03-07T18:19Z', 0.00, lune.PHASE_NEW],
    ['1989-03-14T10:11Z', 0.25, lune.PHASE_FIRST],
    ['1989-03-22T09:58Z', 0.50, lune.PHASE_FULL],
    ['1989-03-30T10:21Z', 0.75, lune.PHASE_LAST],
    ['1989-04-06T03:33Z', 0.00, lune.PHASE_NEW],
    ['1989-04-12T23:13Z', 0.25, lune.PHASE_FIRST],
    ['1989-04-21T03:13Z', 0.50, lune.PHASE_FULL],
    ['1989-04-28T20:46Z', 0.75, lune.PHASE_LAST],
    ['1989-05-05T11:46Z', 0.00, lune.PHASE_NEW],
    ['1989-05-12T14:19Z', 0.25, lune.PHASE_FIRST],
    ['1989-05-20T18:16Z', 0.50, lune.PHASE_FULL],
    ['1989-05-28T04:01Z', 0.75, lune.PHASE_LAST],
    ['1989-06-03T19:53Z', 0.00, lune.PHASE_NEW],
    ['1989-06-11T06:59Z', 0.25, lune.PHASE_FIRST],
    ['1989-06-19T06:57Z', 0.50, lune.PHASE_FULL],
    ['1989-06-26T09:09Z', 0.75, lune.PHASE_LAST],
    ['1989-07-03T04:59Z', 0.00, lune.PHASE_NEW],
    ['1989-07-11T00:19Z', 0.25, lune.PHASE_FIRST],
    ['1989-07-18T17:42Z', 0.50, lune.PHASE_FULL],
    ['1989-07-25T13:31Z', 0.75, lune.PHASE_LAST],
    ['1989-08-01T16:06Z', 0.00, lune.PHASE_NEW],
    ['1989-08-09T17:28Z', 0.25, lune.PHASE_FIRST],
    ['1989-08-17T03:07Z', 0.50, lune.PHASE_FULL],
    ['1989-08-23T18:40Z', 0.75, lune.PHASE_LAST],
    ['1989-08-31T05:44Z', 0.00, lune.PHASE_NEW],
    ['1989-09-08T09:49Z', 0.25, lune.PHASE_FIRST],
    ['1989-09-15T11:51Z', 0.50, lune.PHASE_FULL],
    ['1989-09-22T02:10Z', 0.75, lune.PHASE_LAST],
    ['1989-09-29T21:47Z', 0.00, lune.PHASE_NEW]
  ]

  describe('#phase()', function () {
    it('should return expected values for 2014-02-17', function () {
      const phase = lune.phase(new Date('2014-02-17T00:00-0500'))

      assert.closeTo(phase.phase, 0.568, 0.001)
      assert.closeTo(phase.illuminated, 0.955, 0.001)
      assert.closeTo(phase.age, 16.779, 0.030)
      assert.closeTo(phase.sun_distance, 147822500, 149600)
      assert.closeTo(phase.sun_angular_diameter, 0.5395, 0.0005)
    })

    // Astronomical Algorithms, 2nd ed., p. 347
    it('should return expected values for 1992-04-12', function () {
      const phase = lune.phase(new Date('1992-04-12'))

      assert.closeTo(phase.illuminated, 0.6802, 0.0001)
      assert.closeTo(phase.distance, 368405, 400)
      assert.closeTo(phase.angular_diameter, 0.5405, 0.0005)
    })

    // Astronomical Algorithms, 2nd ed., p. 169
    it('should return expected values for 1992-10-13', function () {
      const phase = lune.phase(new Date('1992-10-13'))

      assert.closeTo(phase.sun_distance, 149240000, 10000)
    })

    // http://bazaar.launchpad.net/~keturn/py-moon-phase/trunk/view/head:/moontest.py
    it('should be accurate to astronomical observations', function () {
      let error = 0
      for (let obs of observations) {
        let e = Math.abs(lune.phase(new Date(obs[0])).phase - obs[1])
        if (e > 0.5) {
          // phase is circular
          e = 1 - e
        }

        error += e
      }

      // tolerate an average error of up to a tenth of a percent
      assert.isAtMost(error / observations.length, 0.001)
    })
  })

  describe('#phase_hunt', function () {
    it('should handle timezones correctly', function () {
      // 1415292777000 incorrect EST time
      // 1415312577000 correct UTC time
      // date conversion is now accurate to the millisecond, but these tests
      // were written when they were only accurate to the second
      assert.closeTo(
        lune.phase_hunt(new Date('2014-11-01T06:26-0400')).full_date.getTime(),
        1415312577000,
        500
      )
    })
  })

  describe('#phase_range', function () {
    const PHASES = [
      lune.PHASE_NEW,
      lune.PHASE_FIRST,
      lune.PHASE_FULL,
      lune.PHASE_LAST
    ]

    /* http://aa.usno.navy.mil/data/docs/JulianDate.php */
    it('should return all moon phases within a time range', function () {
      let error = 0

      for (let phase of PHASES) {
        const actual = lune.phase_range(
          new Date('1989-01-01T00:00Z'),
          new Date('1989-10-01T00:00Z'),
          phase
        )
        const expected = observations.filter(function (obs) {
          return obs[2] === phase
        })
        assert.strictEqual(actual.length, expected.length)

        for (let i = 0; i < actual.length; i++) {
          const date = actual[i]
          const obs = new Date(expected[i][0])

          error += Math.abs(date.getTime() - obs.getTime())
        }
      }

      // tolerate an average error of up to five minutes
      assert.isAtMost(error / observations.length, 300000)
    })
  })
})

describe('julian', function () {
  describe('#fromDate', function () {
    /* http://aa.usno.navy.mil/data/docs/JulianDate.php */
    it('should convert 2000-01-01T00:00Z to 2451544.5', function () {
      assert.closeTo(
        julian.fromDate(new Date('2000-01-01T00:00Z')),
        2451544.5,
        0.5 / 86400
      )
    })
  })

  describe('#toDate', function () {
    /* http://aa.usno.navy.mil/data/docs/JulianDate.php */
    it('should convert 2457464.179862 to 2016-03-16T16:19Z', function () {
      assert.closeTo(
        julian.toDate(2457464.179862).getTime(),
        (new Date('2016-03-16T16:19Z')).getTime(),
        500
      )
    })
  })
})
