import {EventEmitter} from "events"
import TailFollow from "tail-follow"
import {resolve} from "path"
import CatTail from "cat-tail"

export default class LiveIndex extends EventEmitter {
  constructor ({pathToWatch, indexer} = {}) {
    super()

    this._watchPaths = []
    this._indexer = null
    this._tails = new Map()
    this._files = new Map()
    this._index = new Map()
    this._unprocessedChunks = new WeakMap()

    if (pathToWatch) {
      this.addPathToWatch(pathToWatch)
    }

    if (indexer) {
      this.setIndexer(indexer)
    }

    if (pathToWatch && indexer) {
      this.watch()
    }
  }

  addPathToWatch (path) {
    this._watchPaths.push(resolve(path))
  }

  setIndexer (evaluator) {
    this._indexer = evaluator
  }

  watch () {
    this._watchPaths.forEach(path => {
      this.addDataFileWithoutIndexing(path)

      const tail = new TailFollow(path, {
        surviveRotation: true,
        objectMode: true
      })
      this._tails.set(path, tail)

      tail
        .on("data", chunk => this._evaluateChunk(path, chunk))
        .on("rename", (oldPath, newPath) => {
          const renamedFile = this._files.get(oldPath)
          renamedFile.path = newPath
          this._files.set(newPath, renamedFile)
          this._files.set(oldPath, new File(oldPath))
        })
    })
  }

  _evaluateChunk (path, chunk) {
    const indexer = this._indexer
    const tail = this._tails.get(path)
    let pos = tail.positionForChunk(chunk)

    const unprocessedChunk = this._unprocessedChunks.get(tail)
    if (unprocessedChunk) {
      chunk = Buffer.concat([unprocessedChunk, chunk])
      pos = pos - unprocessedChunk.length
      this._unprocessedChunks.delete(tail)
    }

    const processedTo = processedPos => {
      this._unprocessedChunks.set(tail, chunk.slice(processedPos))
    }

    const addIndex = (identifier, positionInChunk) => {
      this.insert(identifier, path, pos + positionInChunk)
    }

    indexer(chunk, addIndex, processedTo)
  }

  insert (identifier, filePath, position) {
    const file = this._files.get(filePath)
    this._index.set(identifier, {file, position})
    this.emit("insert", identifier)
  }

  fileAndPositionForIdentifier (identifier) {
    const result = this._index.get(identifier)
    if (result === undefined) { return undefined }
    return {
      position: result.position,
      file: result.file.path
    }
  }

  addStaticDataFile (path, cb) {
    this.addDataFileWithoutIndexing(path)
    const tail = new TailFollow(resolve(path), { follow: false, objectMode: true })
    this._tails.set(resolve(path), tail)
    tail
      .once("error", err => cb(err))
      .on("data", chunk => this._evaluateChunk(path, chunk))
      .once("end", () => cb(null))
  }

  addDataFileWithoutIndexing (path) {
    const resolvedPath = resolve(path)
    this._files.set(resolvedPath, new File(resolvedPath))
  }

  setIndexStorageObject (obj) {
    this._index = obj
  }

  readStreamFromIndex (identifier) {
    const index = this.fileAndPositionForIdentifier(identifier)
    if (index === undefined) {
      return undefined
    }

    return new CatTail(this._filesStartingAt(index.file), {
      start: index.position
    })
  }

  get files () {
    return [...this._files.entries()].map(f => f[1].path)
  }

  _filesStartingAt (file) {
    let found = false
    return this.files.filter(f => {
      if (found === true || f === file) {
        return found = true
      }
      return false
    })
  }
}

class File {
  constructor (path) {
    this.path = path
  }
}

@@ -3234,41 +3234,215 @@
     
-return %5B...this._files.entries()%5D
+const files = %5B%5D%0A    const fileIterator = this._files.entries()%0A    while (true) %7B%0A      const entry = fileIterator.next()%0A      if (entry.done) %7B break %7D%0A      files.push(entry.value)%0A    %7D%0A    return files
 .map

Fix files getter to not require Array.from, which is not in Node 0.12