#![warn(missing_docs)]
//! Display a range of IP addresses.
//!
//! The `prips` tool takes either a start and end address or a CIDR
//! subnet/prefixlen specification and displays the IPv4 addresses
//! contained within the specified range.
/*
 * Copyright (c) 2021  Peter Pentchev <roam@ringlet.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

use std::env;
use std::error;

use expect_exit::{ExpectedResult, ExpectedWithError};

#[macro_use]
extern crate quick_error;

pub mod defs;
pub mod parse;
pub mod prips;

const VERSION: &str = env!("CARGO_PKG_VERSION");

fn parse_args() -> Result<defs::Config, Box<dyn error::Error>> {
    let args: Vec<String> = env::args().collect();
    let mut optargs = getopts::Options::new();
    optargs.optflag("c", "", "Print the range in CIDR notation");
    optargs.optopt(
        "d",
        "",
        "Set the delimiter to the character with the specified ASCII code",
        "DELIM",
    );
    optargs.optopt(
        "f",
        "",
        "Set the address display format (hex, dec, or dot)",
        "FORMAT",
    );
    optargs.optflag(
        "",
        "features",
        "Display information about the supported features",
    );
    optargs.optflag("h", "help", "Display program usage information and exit");
    optargs.optopt(
        "i",
        "",
        "Increment by the specified number of addresses instead of one",
        "STEP",
    );
    optargs.optflag(
        "v",
        "version",
        "Display program version information and exit",
    );

    let opts = optargs.parse(&args[1..])?;

    if opts.opt_present("v") {
        println!("prips {}", VERSION);
    }
    if opts.opt_present("features") {
        println!("Features: prips={} prips-impl-rust={}", VERSION, VERSION);
    }
    if opts.opt_present("h") {
        println!(
            "{}",
            optargs.usage(
                "Usage:\tprips [-c] [-d delim] [-f format] [-i step] first last\n\
                       \tprips [-c] [-d delim] [-f format] [-i step] base/prefixlen\n\
                       \tprips -h | --help | -v | --version"
            )
        );
    }
    if opts.opt_present("features") || opts.opt_present("h") || opts.opt_present("v") {
        return Ok(defs::Config {
            delim: '\n',
            format: defs::AddrFormat::Dot,
            mode: defs::Mode::Done,
            range: defs::AddrRange { start: 0, end: 0 },
            step: 1,
        });
    }

    let first = opts
        .free
        .get(0)
        .expect_result_("Not enough arguments specified")?;
    let range = match opts.free.get(1) {
        None => parse::parse_cidr(first),
        Some(second) => {
            if opts.free.get(2).is_some() {
                expect_exit::exit("Too many arguments specified");
            }
            parse::parse_range(first, second)
        }
    }?;

    let delim_code: u8 = opts.opt_get_default("d", 10)?;
    let delim = char::from_u32(delim_code as u32).unwrap();

    let step: usize = opts.opt_get_default("i", 1)?;

    let format: defs::AddrFormat = opts.opt_get_default("f", defs::AddrFormat::Dot)?;

    let mode = match opts.opt_present("c") {
        false => defs::Mode::List,
        true => defs::Mode::Cidrize,
    };

    Ok(defs::Config {
        delim,
        format,
        mode,
        range,
        step,
    })
}

fn main() {
    let cfg = parse_args().or_exit_e_("Could not parse the command-line arguments");
    match cfg.mode {
        defs::Mode::Done => (),
        defs::Mode::List => prips::output_range(cfg),
        defs::Mode::Cidrize => prips::cidrize(cfg),
    };
}
