/*****************************************************************************
 *
 *  Copyright (C) 2006-2014  Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  This file is part of the IgH EtherCAT Master.
 *
 *  The IgH EtherCAT Master is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License version 2, as
 *  published by the Free Software Foundation.
 *
 *  The IgH EtherCAT Master is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 *  Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with the IgH EtherCAT Master; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  vim: expandtab
 *
 ****************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include <iostream>
#include <algorithm>
using namespace std;

#include "CommandIp.h"
#include "MasterDevice.h"

/****************************************************************************/

CommandIp::CommandIp():
    Command("ip", "Set EoE IP parameters.")
{
}

/****************************************************************************/

string CommandIp::helpString(const string &binaryBaseName) const
{
    stringstream str;

    str << binaryBaseName << " " << getName() << " [OPTIONS] <ARGS>" << endl
        << endl
        << getBriefDescription() << endl
        << endl
        << "This command requires a single slave to be selected." << endl
        << endl
        << "IP parameters can be appended as argument pairs:" << endl
        << endl
        << "  ip_address <IPv4>[/prefix]  IP address (optionally with" << endl
        << "                                decimal subnet prefix)" << endl
        << "  mac_address <MAC>           Link-layer address (may contain"
        << endl
        << "                                colons or hyphens)" << endl
        << "  default_gateway <IPv4>      Default gateway" << endl
        << "  dns_address <IPv4>          DNS server address" << endl
        << "  hostname <hostname>         Host name (max. 32 byte)" << endl
        << endl
        << "IPv4 adresses can be given either in dot notation or as" << endl
        << "hostnames, which will be automatically resolved." << endl
        << endl
        << "Command-specific options:" << endl
        << "  --alias    -a <alias>" << endl
        << "  --position -p <pos>    Slave selection. See the help of" << endl
        << "                         the 'slaves' command." << endl
        << endl
        << numericInfo();

    return str.str();
}

/****************************************************************************/

void CommandIp::execute(const StringVector &args)
{
    if (args.size() <= 0) {
        return;
    }

    if (args.size() % 2) {
        stringstream err;
        err << "'" << getName() << "' needs an even number of arguments!";
        throwInvalidUsageException(err);
    }

    ec_ioctl_eoe_ip_t io = {};

    for (unsigned int argIdx = 0; argIdx < args.size(); argIdx += 2) {
        string arg = args[argIdx];
        string val = args[argIdx + 1];
        std::transform(arg.begin(), arg.end(), arg.begin(), ::tolower);

        if (arg == "link" or arg == "mac_address") {
            parseMac(io.mac_address, val);
            io.mac_address_included = 1;
        }
        else if (arg == "addr" or arg == "ip_address") {
            parseIpv4Prefix(&io, val);
            io.ip_address_included = 1;
        }
        else if (arg == "default" or arg == "default_gateway") {
            resolveIpv4(&io.gateway, val);
            io.gateway_included = 1;
        }
        else if (arg == "dns" or arg == "dns_adress") {
            resolveIpv4(&io.dns, val);
            io.dns_included = 1;
        }
        else if (arg == "name" or arg == "hostname") {
            if (val.size() > EC_MAX_HOSTNAME_SIZE - 1) {
                stringstream err;
                err << "Name too long!";
                throwInvalidUsageException(err);
            }
            unsigned int i;
            for (i = 0; i < val.size(); i++) {
                io.name[i] = val[i];
            }
            io.name[i] = 0;
            io.name_included = 1;
        }
        else {
            stringstream err;
            err << "Unknown argument '" << args[argIdx] << "'!";
            throwInvalidUsageException(err);
        }
    }

    MasterDevice m(getSingleMasterIndex());
    m.open(MasterDevice::ReadWrite);
    SlaveList slaves = selectedSlaves(m);
    if (slaves.size() != 1) {
        throwSingleSlaveRequired(slaves.size());
    }
    io.slave_position = slaves.front().position;

    // execute actual request
    try {
        m.setIpParam(&io);
    } catch (MasterDeviceException &e) {
        throw e;
    }
}

/****************************************************************************/

void CommandIp::parseMac(unsigned char mac[EC_ETH_ALEN], const string &str)
{
    unsigned int pos = 0;

    for (unsigned int i = 0; i < EC_ETH_ALEN; i++) {
        if (pos + 2 > str.size()) {
            stringstream err;
            err << "Incomplete MAC address!";
            throwInvalidUsageException(err);
        }

        string byteStr = str.substr(pos, 2);
        pos += 2;

        stringstream s;
        s << byteStr;
        unsigned int byteValue;
        s >> hex >> byteValue;
        if (s.fail() || !s.eof() || byteValue > 0xff) {
            stringstream err;
            err << "Invalid MAC address!";
            throwInvalidUsageException(err);
        }
        mac[i] = byteValue;

        while (pos < str.size() && (str[pos] == ':' || str[pos] == '-')) {
            pos++;
        }
    }
}

/****************************************************************************/

void CommandIp::parseIpv4Prefix(ec_ioctl_eoe_ip_t *io,
        const string &str)
{
    size_t pos = str.find('/');
    string host;

    io->subnet_mask_included = pos != string::npos;

    if (pos == string::npos) { // no prefix found
        host = str;
    }
    else {
        host = str.substr(0, pos);
        string prefixStr = str.substr(pos + 1, string::npos);
        stringstream s;
        s << prefixStr;
        unsigned int prefix;
        s >> prefix;
        if (s.fail() || !s.eof() || prefix > 32) {
            stringstream err;
            err << "Invalid prefix '" << prefixStr << "'!";
            throwInvalidUsageException(err);
        }
        uint32_t mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
        io->subnet_mask.s_addr = htonl(mask);
    }

    resolveIpv4(&io->ip_address, host);
}

/****************************************************************************/

void CommandIp::resolveIpv4(struct in_addr *dst, const string &str)
{
    struct addrinfo hints = {};
    struct addrinfo *res;

    hints.ai_family = AF_INET; // only IPv4

    int ret = getaddrinfo(str.c_str(), NULL, &hints, &res);
    if (ret) {
        stringstream err;
        err << "Lookup of '" << str << "' failed: "
            << gai_strerror(ret) << endl;
        throwCommandException(err.str());
    }

    if (!res) { // returned list is empty
        stringstream err;
        err << "Lookup of '" << str << "' failed." << endl;
        throwCommandException(err.str());
    }

    const struct sockaddr_in *in_addr =
        (const struct sockaddr_in *) res->ai_addr;
    *dst = in_addr->sin_addr;
    freeaddrinfo(res);
}

/****************************************************************************/
