import binary from 'binary'
import {ParseConnectBufferParameters} from '../types/parse-connect-buffer'
import {RFC_1928_ATYP, RFC_1928_REPLIES, RFC_1928_VERSION} from './constants'

const LENGTH_RFC_1928_ATYP = 4

export function parseConnectBuffer({
  buffer,
  onReqesutBufferParsed,
  onParseCompleted,
  end,
}: ParseConnectBufferParameters) {
  const binaryStream = binary.stream(buffer)

  binaryStream
    .word8('ver')
    .word8('cmd')
    .word8('rsv')
    .word8('atyp')
    .tap((args: any) => {
      // capture the raw buffer
      //debugger
      args.requestBuffer = buffer

      // verify version is appropriate
      if (args.ver !== RFC_1928_VERSION) {
        return end(RFC_1928_REPLIES.GENERAL_FAILURE, args)
      }

      // append socket to active sessions
      onReqesutBufferParsed()

      // create dst
      args.dst = {}

      // ipv4
      if (args.atyp === RFC_1928_ATYP.IPV4) {
        binaryStream.buffer('addr.buf', LENGTH_RFC_1928_ATYP).tap((args: any) => {
          args.dst.addr = [].slice.call(args.addr.buf).join('.')
        })

        // domain name
      } else if (args.atyp === RFC_1928_ATYP.DOMAINNAME) {
        binaryStream
          .word8('addr.size')
          .buffer('addr.buf', 'addr.size')
          .tap((args: any) => {
            args.dst.addr = args.addr.buf.toString()
          })

        // ipv6
      } else if (args.atyp === RFC_1928_ATYP.IPV6) {
        binaryStream
          .word32be('addr.a')
          .word32be('addr.b')
          .word32be('addr.c')
          .word32be('addr.d')
          .tap((args: any) => {
            args.dst.addr = []

            // extract the parts of the ipv6 address
            ;['a', 'b', 'c', 'd'].forEach((x) => {
              x = args.addr[x]

              // convert DWORD to two WORD values and append
              /* eslint no-magic-numbers : 0 */
              args.dst.addr.push(((x as any) >>> 16).toString(16))
              args.dst.addr.push(((x as any) & 0xffff).toString(16))
            })

            // format ipv6 address as string
            args.dst.addr = args.dst.addr.join(':')
          })

        // unsupported address type
      } else {
        return end(RFC_1928_REPLIES.ADDRESS_TYPE_NOT_SUPPORTED, args)
      }
    })
    .word16bu('dst.port')
    .tap((args: any) => {
      onParseCompleted(args)
    })
}
