// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
const common = require('../common');
const assert = require('assert');
const net = require('net');

// Test allowHalfOpen
{
  let clientReceivedFIN = 0;
  let serverConnections = 0;
  let clientSentFIN = 0;
  let serverReceivedFIN = 0;
  const host = common.localhostIPv4;

  function serverOnConnection(socket) {
    console.log(`'connection' ${++serverConnections} emitted on server`);
    const srvConn = serverConnections;
    socket.resume();
    socket.on('data', common.mustCall(function socketOnData(data) {
      this.clientId = data.toString();
      console.log(
        `server connection ${srvConn} is started by client ${this.clientId}`);
    }));
    // 'end' on each socket must not be emitted twice
    socket.on('end', common.mustCall(function socketOnEnd() {
      console.log(`Server received FIN sent by client ${this.clientId}`);
      if (++serverReceivedFIN < CLIENT_VARIANTS) return;
      setTimeout(() => {
        server.close();
        console.log(`connection ${this.clientId} is closing the server:
          FIN ${serverReceivedFIN} received by server,
          FIN ${clientReceivedFIN} received by client
          FIN ${clientSentFIN} sent by client,
          FIN ${serverConnections} sent by server`.replace(/ {3,}/g, ''));
      }, 50);
    }, 1));
    socket.end();
    console.log(`Server has sent ${serverConnections} FIN`);
  }

  // These two levels of functions (and not arrows) are necessary in order to
  // bind the `index`, and the calling socket (`this`)
  function clientOnConnect(index) {
    return common.mustCall(function clientOnConnectInner() {
      const client = this;
      console.log(`'connect' emitted on Client ${index}`);
      client.resume();
      client.on('end', common.mustCall(function clientOnEnd() {
        setTimeout(common.mustCall(() => {
          // When allowHalfOpen is true, client must still be writable
          // after the server closes the connections, but not readable
          console.log(`client ${index} received FIN`);
          assert(!client.readable);
          assert(client.writable);
          assert(client.write(String(index)));
          client.end();
          clientSentFIN++;
          console.log(
            `client ${index} sent FIN, ${clientSentFIN} have been sent`);
        }), 50);
      }));
      client.on('close', common.mustCall(function clientOnClose() {
        clientReceivedFIN++;
        console.log(`connection ${index} has been closed by both sides,` +
          ` ${clientReceivedFIN} clients have closed`);
      }));
    });
  }

  function serverOnClose() {
    console.log(`Server has been closed:
      FIN ${serverReceivedFIN} received by server
      FIN ${clientReceivedFIN} received by client
      FIN ${clientSentFIN} sent by client
      FIN ${serverConnections} sent by server`.replace(/ {3,}/g, ''));
  }

  function serverOnListen() {
    const port = server.address().port;
    console.log(`Server started listening at ${host}:${port}`);
    const opts = { allowHalfOpen: true, host, port };
    // 6 variations === CLIENT_VARIANTS
    net.connect(opts, clientOnConnect(1));
    net.connect(opts).on('connect', clientOnConnect(2));
    net.createConnection(opts, clientOnConnect(3));
    net.createConnection(opts).on('connect', clientOnConnect(4));
    new net.Socket(opts).connect(opts, clientOnConnect(5));
    new net.Socket(opts).connect(opts).on('connect', clientOnConnect(6));
  }

  const CLIENT_VARIANTS = 6;

  // The trigger
  const server = net.createServer({ allowHalfOpen: true })
    .on('connection', common.mustCall(serverOnConnection, CLIENT_VARIANTS))
    .on('close', common.mustCall(serverOnClose))
    .listen(0, host, common.mustCall(serverOnListen));
}
