// written by crushv <nik@telekem.net>
// thanks nikki

const net = require('net')
const EventEmitter = require('events')

const socket = new net.Socket()
const emitter = new EventEmitter()

socket.setEncoding('utf8')

socket.on('error', console.error)

function m (text) {
  console.log('> ' + text)
  socket.write(text + '\r\n')
}

var config

socket.once('connect', async () => {
  console.log('Connected')
  m(`PASS ${config.pass} TS 6 :${config.sid}`)
  m('CAPAB QS ENCAP EX IE SAVE EUID')
  m(`SERVER ${config.server} 1 satyr`)
})

function parseLine (l) {
  const colIndex = l.lastIndexOf(':')
  if (colIndex > -1) {
    return {
      params: l.substring(0, colIndex - 1).split(' '),
      query: l.substring(colIndex + 1)
    }
  } else return { params: l.split(' ') }
}

const servers = []
const users = {}
const channels = {}

const globalCommands = {
  // PING :42X
  // params: SID
  PING: l => {
    const { query } = parseLine(l)
    m(`PONG :${query}`)
    emitter.emit('ping')
  },
  // PASS hunter2 TS 6 :42X
  // params: password, 'TS', TS version, SID
  PASS: l => {
    const { query } = parseLine(l)
    // adds a server
    servers.push(query)
  }
}

const serverCommands = {
  // EUID nik 1 1569146316 +i ~nik localhost6.attlocal.net 0::1 42XAAAAAB * * :nik
  // params: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address, UID, real hostname, account name, gecos
  EUID: l => {
    const { params } = parseLine(l)
    const user = {
      nick: params[0],
      nickTS: params[2],
      modes: params[3],
      username: params[4],
      vhost: params[5],
      ip: params[6],
      uid: params[7]
    }
    users[user.uid] = user
  },
  // SJOIN 1569142987 #test +nt :42XAAAAAB
  // params: channelTS, channel, simple modes, opt. mode parameters..., nicklist
  SJOIN: l => {
    const { params, query } = parseLine(l)
    const channel = {
      timestamp: params[0],
      name: params[1],
      modes: params.slice(2).join(' '),
      nicklist: query.split(' ').map(uid => {
        if (/[^0-9a-zA-Z]/.test(uid[0])) return { uid: uid.slice(1), mode: uid[0] }
        else return { uid: uid, mode: '' }
      })
    }
    channels[channel.name] = channel
  }
}

const userCommands = {
  // :42XAAAAAC PRIVMSG #test :asd
  // params: target, msg
  PRIVMSG: (l, source) => {
    const { params, query } = parseLine(l)
    emitter.emit('message', users[source].nick, params[0], query)
  },
  // :42XAAAAAC JOIN 1569149395 #test +
  JOIN: (l, source) => {
    const { params } = parseLine(l)
    channels[params[1]].nicklist.push({
      uid: source
    })
  },
  // :42XAAAAAC PART #test :WeeChat 2.6
  PART: (l, source) => {
    const { params } = parseLine(l)
    for (let i = 0; i < channels[params[0]].nicklist.length; i++) {
      if (channels[params[0]].nicklist[i].uid === source) {
        channels[params[0]].nicklist.splice(i, 1)
        return
      }
    }
  },
  QUIT: (_l, source) => {
    delete users[source]
  }
}

function parser (l) {
  const split = l.split(' ')
  const cmd = split[0]
  const args = split.slice(1).join(' ')
  if (globalCommands[cmd]) return globalCommands[cmd](args)
  if (cmd[0] === ':') {
    const source = cmd.slice(1)
    const subcmd = split[1]
    const subargs = split.slice(2).join(' ')
    if (servers.indexOf(source) > -1 && serverCommands[subcmd]) serverCommands[subcmd](subargs)
    if (users[source] && userCommands[subcmd]) userCommands[subcmd](subargs, source)
  }
}

socket.on('data', data => {
  data.split('\r\n')
    .filter(l => l !== '')
    .forEach(l => {
      console.log('< ' + l)
      parser(l)
    })
})

module.exports.connect = conf => new Promise((resolve, reject) => {
  emitter.once('ping', resolve)
  config = conf
  socket.connect(config.port)
  process.on('SIGINT', () => {
    socket.write('QUIT\r\n')
    process.exit()
  })
})
module.exports.events = emitter

const genTS = () => Math.trunc((new Date()).getTime() / 1000)
const genUID = () => {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  var uid = ''
  for (let i = 0; i < 6; i++) uid += chars.charAt(Math.floor(Math.random() * chars.length))
  if (users[uid]) return genUID()
  return config.sid + uid
}
const getUID = nick => {
  for (const key in users) if (users[key].nick === nick) return key
}

module.exports.registerUser = nick => {
  const user = {
    nick: nick,
    nickTS: genTS(),
    modes: '+i',
    username: '~' + nick,
    vhost: config.vhost,
    ip: '0::1',
    uid: genUID()
  }
  users[user.uid] = user
  m(`EUID ${user.nick} 1 ${user.nickTS} ${user.modes} ~${user.nick} ${user.vhost} 0::1 ${user.uid} * * :${user.nick}`)
}
module.exports.unregisterUser = nick => {
  const uid = getUID(nick)
  m(`:${uid} QUIT :Quit: satyr`)
  delete users[uid]
}
module.exports.join = (nick, channelName) => {
  const uid = getUID(nick)
  if (!channels[channelName]) {
    const channel = {
      timestamp: genTS(),
      name: channelName,
      modes: '+nt',
      nicklist: [{ uid: uid, mode: '' }]
    }
    channels[channel.name] = channel
  }
  m(`:${uid} JOIN ${channels[channelName].timestamp} ${channelName} +`)
}
module.exports.part = (nick, channelName) => {
  const uid = getUID(nick)
  m(`:${uid} PART ${channelName} :satyr`)
  for (let i = 0; i < channels[channelName].nicklist.length; i++) {
    if (channels[channelName].nicklist[i].uid === uid) {
      channels[channelName].nicklist.splice(i, 1)
      return
    }
  }
}
module.exports.send = (nick, channelName, message) => {
  const uid = getUID(nick)
  m(`:${uid} PRIVMSG ${channelName} :${message}`)
  emitter.emit('message', nick, channelName, message)
}
