const fs = require('fs')
const ndjson = require('ndjson')
const _ = require('lodash/fp')

const mapAliases = _.curry((iteratee, individuals) => {
  const mapIndividual = individual => {
    const { id, aliases } = individual
    return _.map(alias => iteratee(id, alias), aliases)
  }
  return _.flatMap(mapIndividual, individuals)
})

const getPhoneticEntries = (individualId, alias) => {
  const pairPhoneticsWithValues = word => {
    const { value, phonetics } = word
    const makeEntry = phonetic => ({ value, phonetic, aliasId: alias.id })
    return _.map(makeEntry, phonetics)
  }
  return _.flatMap(pairPhoneticsWithValues, alias.words)
}

const producePhoneticMap = _.flow(
  mapAliases(getPhoneticEntries),
  _.flatten,
  _.groupBy(_.get('phonetic')),
  _.mapValues(_.flow(_.map(_.get('aliasId')), _.uniq)),
  _.toPairs,
  entries => new Map(entries),
)

const getWords = (individualId, alias) => {
  const pairWordsWithIds = word => ({ value: word.value, aliasId: alias.id })
  return _.map(pairWordsWithIds, alias.words)
}

const produceWordList = _.flow(
  mapAliases(getWords),
  _.flatten,
  _.groupBy(_.get('value')),
  _.mapValues(_.map(_.get('aliasId'))),
  _.toPairs,
  _.map(_.zipObject(['value', 'aliasIds'])),
)

const parseSource = source => {
  const individuals = []

  const readStream = fs.createReadStream(source)
  const jsonStream = readStream.pipe(ndjson.parse())
  jsonStream.on('data', individual => {
    _.forEach(period => {
      _.forEach(
        date => {
          const { year, month, day } = date
          date.date = new Date(year, month - 1, day)
        },
        [period.start, period.end],
      )
    }, individual.birthDatePeriods)
    individuals.push(individual)
  })

  return new Promise((resolve, reject) => {
    jsonStream.on('error', reject)
    jsonStream.on('end', () => {
      resolve(individuals)
    })
  })
}

const load = sources =>
  Promise.all(_.map(parseSource, sources)).then(
    _.flow(_.flatten, _.compact, _.uniqBy(_.get('id')), individuals => {
      const individualsMap = _.flow(
        _.groupBy(_.get('id')),
        _.mapValues(_.first),
        _.toPairs,
        entries => new Map(entries),
      )(individuals)

      const makeEntries = (individualId, alias) => [alias.id, alias]
      const aliasesMap = new Map(mapAliases(makeEntries, individuals))

      const getIdPairs = (individualId, alias) => [alias.id, individualId]
      const idPairs = mapAliases(getIdPairs, individuals)
      const aliasToIndividual = new Map(idPairs)

      const phoneticMap = producePhoneticMap(individuals)
      const wordList = produceWordList(individuals)

      return {
        individuals,
        individualsMap,
        aliasesMap,
        aliasToIndividual,
        phoneticMap,
        wordList,
      }
    }),
  )

module.exports = { load }
