/*
  Rewritten from: https://github.com/TooTallNate/node-socks-proxy-agent
*/

import * as dns from 'dns';
import * as tls from 'tls';
import * as url from 'url';

import * as agentBase from 'agent-base';
import { SocksClient } from 'socks';
import { SocksCommandOption, SocksProxyType } from 'socks/typings/common/constants';

export interface IAuthentication {
  username?: string;
  password: string;
}

export interface IProxyInfo extends url.UrlObjectCommon {
  // destination: SocksRemoteHost;
  port: number;
  version: number; // Socks Version.
  authentication?: IAuthentication;
  lookup: boolean;
  command: SocksCommandOption;
  timeout?: number;
}

/**
 * The `SocksProxyAgent`.
 *
 * @api public
 */

export class SocksProxyAgent extends agentBase {
  public maxFreeSockets: number;
  public maxSockets: number;
  public requests: any; // tslint:disable-line no-any
  public sockets: any; // tslint:disable-line no-any

  private proxy: IProxyInfo;

  constructor(options?: IProxyInfo) {
    super(options);

    this.proxy = options;
  }

  public destroy() {} // tslint:disable-line no-empty

  /**
   * Initiates a SOCKS connection to the specified SOCKS proxy server,
   * which in turn connects to the specified remote host and port.
   *
   * @api public
   */
  public callback(req, opts, fn) {
    const proxy = this.proxy;

    // called once the SOCKS proxy has connected to the specified remote endpoint
    function onhostconnect(err, result) {
      if (err) return fn(err);

      const socket = result.socket;
      let s = socket;
      if (opts.secureEndpoint) {
        // since the proxy is connecting to an SSL server, we have
        // to upgrade this socket connection to an SSL connection
        opts.socket = socket;
        opts.servername = opts.host;
        opts.host = null;
        opts.hostname = null;
        opts.port = null;
        s = tls.connect(opts);
      }

      fn(null, s);
    }

    // called for the `dns.lookup()` callback
    function onlookup(err: Error, ip: string) {
      if (err) return fn(err);
      options.destination.host = ip;
      SocksClient.createConnection(options, onhostconnect);
    }

    const options = {
      command: 'connect' as SocksCommandOption,
      destination: {
        host: undefined,
        port: +opts.port,
      },
      proxy: {
        ipaddress: proxy.host,
        password: undefined,
        port: +proxy.port,
        type: proxy.version as SocksProxyType,
        userId: undefined,
      },
    };

    if (proxy.authentication) {
      options.proxy.userId = proxy.authentication.username;
      options.proxy.password = proxy.authentication.password;
    }

    if (proxy.lookup) {
      // client-side DNS resolution for "4" and "5" socks proxy versions
      dns.lookup(opts.host, onlookup);
    } else {
      // proxy hostname DNS resolution for "4a" and "5h" socks proxy servers
      onlookup(null, opts.host);
    }
  }
}
