import fs from 'fs'
import path from 'path'

interface Error {
  exists: boolean
  message: string
}

export default class DB {

  private name: string
  private data: any[] = []
  private error: Error = { exists: false, message: '' }
  private selected: any[] = []
  private selection: boolean = false

  protected constructor (name: string) {
    this.name = name + '.json'
    this.makesure()
  }

  protected makesure () {
    const filename = path.join(path.resolve(), 'dbs', this.name)

    if ( !fs.existsSync(filename) ) fs.writeFileSync(filename, '[]')
  }

  protected rewrite (affectedIDs: number[] | number) {
    const filename = path.join(path.resolve(), 'dbs', this.name)
    const datastring = JSON.stringify(this.data)

    try {
      fs.writeFileSync(filename, datastring)
      console.log('Database re-written')
    } catch {
      this.error = { exists: true, message: 'Failed to re-write database!' }
    }

    this.end()
   
    return this.error.exists ? this.error : { ok: true, affectedIDs }
  }

  protected getselected (field: string) {
    return this.selected.map((selected: any) => selected[field])
  }

  protected getincrementid () {
    const lastdata = this.data[this.data.length -1]
    return lastdata ? lastdata.id + 1 : 1
  }

  protected loopcreate (newdata: any[], status: any[]) {
    newdata.forEach((data: any) => {
      const created = this.create(data)
      status.push(created)
    })
    return status
  }

  protected end () {
    this.selected = []
    this.selection = false
  }

  public create (newdata: any) {

    if ( Array.isArray(newdata) ) return this.loopcreate(newdata, [])

    this.data = this.read()

    const id = this.getincrementid()

    this.data.push({ id, ...newdata })
    
    return this.rewrite(id)
  }

  public read () {
    if ( this.selection ) return this.selected

    const filename: string = path.join(path.resolve(), 'dbs', this.name)
    const datastring: string = fs.readFileSync(filename, 'utf8')

    return JSON.parse(datastring)
  }

  public update (newdata: any) {
    const selectedids = this.getselected('id')

    if ( !selectedids.length ) return { ok: false, message: 'No item selected!' }
    
    this.data = this.data.map((data: any) => {
      if ( selectedids.includes(data.id) )
        for ( let [key, val] of Object.entries(newdata) )
          data[key] = val
      return data
    })

    return this.rewrite(selectedids)
  }

  public delete () {
    const selectedids = this.getselected('id')

    if ( !selectedids.length ) return { ok: false, message: 'No item selected' }
    
    this.data = this.data.filter((data: any) => {
      if ( !selectedids.includes(data.id) ) return data
    })

    return this.rewrite(selectedids)
  }

  public where (callback: any) {
    this.selection = false
    this.data = this.read()
    this.selected = this.data.filter(callback)
    this.selection = true
    return this
  }

  public join (other: string, field: string) {
    const selectedids = this.getselected('id')

    if ( !selectedids.length ) {
      console.log('No selected items')
      return []
    } else console.log('Joining into ' + other)

    const selecteddata = this.selected.map(selected => {
      const joinmodel = new Join(other)
      const joindata = joinmodel.where((data: any) => selectedids.includes(data[field])).read()

      selected[other] = joindata
      return selected
    })

    this.end()

    return selecteddata
  }

}

class Join extends DB {

  public constructor (name: string) {
    super(name)
  }

}