/** @file
    WCCP cache client

    @section license License

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <memory.h>
#include <strings.h>
#include <iostream>
#include <iomanip>

#include <getopt.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <poll.h>

#include "ts/ink_memory.h"
#include "Wccp.h"
#include "WccpUtil.h"
#include "tsconfig/TsValue.h"
#include "ts/ink_lockfile.h"

#define WCCP_LOCK "wccp.pid"

bool do_debug  = false;
bool do_daemon = false;

static const char USAGE_TEXT[] = "%s\n"
                                 "--address IP address to bind.\n"
                                 "--router Booststrap IP address for routers.\n"
                                 "--service Path to service group definitions.\n"
                                 "--debug Print debugging information.\n"
                                 "--daemon Run as daemon.\n"
                                 "--help Print usage and exit.\n";

static void
PrintErrata(ts::Errata const &err)
{
  size_t n;
  static size_t const SIZE = 4096;
  char buff[SIZE];
  if (err.size()) {
    ts::Errata::Code code = err.top().getCode();
    if (do_debug || code >= wccp::LVL_WARN) {
      n = err.write(buff, SIZE, 1, 0, 2, "> ");
      // strip trailing newlines.
      while (n && (buff[n - 1] == '\n' || buff[n - 1] == '\r'))
        buff[--n] = 0;
      printf("%s\n", buff);
    }
  }
}

static void
Init_Errata_Logging()
{
  ts::Errata::registerSink(&PrintErrata);
}

static void
check_lockfile()
{
  char lockfile[256];
  pid_t holding_pid;
  int err;

  strcpy(lockfile, "/var/run/");
  strcat(lockfile, WCCP_LOCK);

  Lockfile server_lockfile(lockfile);
  err = server_lockfile.Get(&holding_pid);

  if (err != 1) {
    char *reason = strerror(-err);
    fprintf(stderr, "WARNING: Can't acquire lockfile '%s'", (const char *)lockfile);

    if ((err == 0) && (holding_pid != -1)) {
      fprintf(stderr, " (Lock file held by process ID %l" PRIu32 ")\n", (long)holding_pid);
    } else if ((err == 0) && (holding_pid == -1)) {
      fprintf(stderr, " (Lock file exists, but can't read process ID)\n");
    } else if (reason) {
      fprintf(stderr, " (%s)\n", reason);
    } else {
      fprintf(stderr, "\n");
    }
    ::exit(1);
  }
}

int
main(int argc, char **argv)
{
  wccp::Cache wcp;

  // getopt return values. Selected to avoid collisions with
  // short arguments.
  static int const OPT_ADDRESS = 257; ///< Bind to IP address option.
  static int const OPT_HELP    = 258; ///< Print help message.
  static int const OPT_ROUTER  = 259; ///< Seeded router IP address.
  static int const OPT_SERVICE = 260; ///< Service group definition.
  static int const OPT_DEBUG   = 261; ///< Enable debug printing
  static int const OPT_DAEMON  = 262; ///< Disconnect and run as daemon

  static option OPTIONS[] = {
    {"address", 1, 0, OPT_ADDRESS},
    {"router", 1, 0, OPT_ROUTER},
    {"service", 1, 0, OPT_SERVICE},
    {"debug", 0, 0, OPT_DEBUG},
    {"daemon", 0, 0, OPT_DAEMON},
    {"help", 0, 0, OPT_HELP},
    {0, 0, 0, 0} // required terminator.
  };

  in_addr ip_addr     = {INADDR_ANY};
  in_addr router_addr = {INADDR_ANY};

  int zret; // getopt return.
  int zidx; // option index.
  bool fail            = false;
  const char *FAIL_MSG = "";

  while (-1 != (zret = getopt_long_only(argc, argv, "", OPTIONS, &zidx))) {
    switch (zret) {
    case OPT_HELP:
      FAIL_MSG = "Usage:";
      fail     = true;
      break;
    case '?':
      FAIL_MSG = "Invalid option specified.";
      fail     = true;
      break;
    case OPT_ADDRESS:
      if (0 == inet_aton(optarg, &ip_addr)) {
        FAIL_MSG = "Invalid IP address specified for client.";
        fail     = true;
      }
      break;
    case OPT_ROUTER:
      if (0 == inet_aton(optarg, &router_addr)) {
        FAIL_MSG = "Invalid IP address specified for router.";
        fail     = true;
      }
      break;
    case OPT_SERVICE: {
      ts::Errata status = wcp.loadServicesFromFile(optarg);
      if (!status)
        fail = true;
      break;
    }
    case OPT_DEBUG:
      do_debug = true;
      break;
    case OPT_DAEMON:
      do_daemon = true;
      break;
    }
  }

  if (fail) {
    printf(USAGE_TEXT, FAIL_MSG);
    return 1;
  }

  if (0 > wcp.open(ip_addr.s_addr)) {
    fprintf(stderr, "Failed to open or bind socket.\n");
    return 2;
  }

  if (do_daemon) {
    pid_t pid = fork();
    if (pid > 0) {
      // Successful, the parent should go away
      ::exit(0);
    }
  }

  check_lockfile();

  // Set up erratum support.
  Init_Errata_Logging();

  static int const POLL_FD_COUNT = 1;
  pollfd pfa[POLL_FD_COUNT];

  // Poll on the socket.
  pfa[0].fd     = wcp.getSocket();
  pfa[0].events = POLLIN;

  wcp.housekeeping();

  while (true) {
    int n = poll(pfa, POLL_FD_COUNT, 1000);
    if (n < 0) { // error
      perror("General polling failure");
      return 5;
    } else if (n > 0) { // things of interest happened
      if (pfa[0].revents) {
        if (pfa[0].revents & POLLIN) {
          wcp.handleMessage();
        } else {
          fprintf(stderr, "Socket failure.\n");
          return 6;
        }
      }
    } else { // timeout
      wcp.housekeeping();
    }
  }
}
