/*
    Copyright (C) 2022 John Goerzen <jgoerzen@complete.org>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

use crate::header::{CommandDataV1, CommandTypeV1, FSMetaV1, ENV_PREFIX};
use crate::jobfile;
use crate::seqfile;
use anyhow;
use clap::Args;
use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;

/// The options relating to the state file
#[derive(Debug, Args)]
pub struct SFOpts {
    /// The path to the state file.  Set to "-" to not use any state file at all.
    #[arg(short, long, value_name = "FILE")]
    pub seqfile: OsString,
}

/// The options for setting the next value in the sequence file
#[derive(Debug, Args)]
pub struct SFSetOpts {
    #[clap(flatten)]
    sfopts: SFOpts,

    /// The ID to set it to.
    #[arg(value_name = "ID")]
    pub nextid: u64,
}

/** The options passed to a prepare operation.  These can be directly
parsed from the CLI. */
#[derive(Debug, Args)]
pub struct PrepareOpts {
    #[clap(flatten)]
    pub sfopts: SFOpts,

    /// Path to file to use for input.  Set to "-" to use stdin.  If not given, no input is used.
    #[arg(short, long, value_name = "INPUT")]
    pub input: Option<OsString>,

    /// Additional parameters to send along, if any
    #[arg(last = true)]
    pub params: Vec<OsString>,
}

/// Print the sequence number
pub fn cmd_preparegetnext(opts: &SFOpts) -> Result<(), anyhow::Error> {
    let mut lock = seqfile::prepare_seqfile_lock(&opts.seqfile, true)?;

    let seqf = seqfile::SeqFile::open(&opts.seqfile, &mut lock)?;
    println!("{}", seqf.get_next());
    Ok(())
}

/// Set the next sequence number
pub fn cmd_preparesetnext(opts: &SFSetOpts) -> Result<(), anyhow::Error> {
    let mut lock = seqfile::prepare_seqfile_lock(&opts.sfopts.seqfile, true)?;

    let mut seqf = seqfile::SeqFile::open(&opts.sfopts.seqfile, &mut lock)?;
    seqf.set(opts.nextid)?;
    Ok(())
}

/// Prepare a NOP packet
pub fn cmd_preparenop(opts: &SFOpts) -> Result<(), anyhow::Error> {
    let mut lock = None;
    let mut sf = None;
    if opts.seqfile != "-" {
        lock = Some(seqfile::prepare_seqfile_lock(&opts.seqfile, true)?);
        sf = Some(seqfile::SeqFile::open(
            &opts.seqfile,
            lock.as_mut().unwrap(),
        )?);
    };

    let meta = FSMetaV1 {
        seq: match sf {
            None => 1,
            Some(ref mut x) => x.increment()?,
        },
        cmd_type: CommandTypeV1::NOP,
        ..FSMetaV1::default()
    };

    // Release the lock.  It's already incremented, so why not?
    std::mem::drop(sf);
    std::mem::drop(lock);

    let mut fd_stdout = std::io::stdout();
    jobfile::write_jobfile_header(meta, &mut fd_stdout)
}

/// Prepare a Fail packet
pub fn cmd_preparefail(opts: &SFOpts) -> Result<(), anyhow::Error> {
    let mut lock = None;
    let mut sf = None;
    if opts.seqfile != "-" {
        lock = Some(seqfile::prepare_seqfile_lock(&opts.seqfile, true)?);
        sf = Some(seqfile::SeqFile::open(
            &opts.seqfile,
            lock.as_mut().unwrap(),
        )?);
    };

    let meta = FSMetaV1 {
        seq: match sf {
            None => 1,
            Some(ref mut x) => x.increment()?,
        },
        cmd_type: CommandTypeV1::Fail,
        ..FSMetaV1::default()
    };

    // Release the lock.  It's already incremented, so why not?
    std::mem::drop(sf);
    std::mem::drop(lock);

    let mut fd_stdout = std::io::stdout();
    jobfile::write_jobfile_header(meta, &mut fd_stdout)
}

/// Prepare a job packet
pub fn cmd_prepare(opts: &PrepareOpts) -> Result<(), anyhow::Error> {
    let mut lock = None;
    let mut sf = None;
    if opts.sfopts.seqfile != "-" {
        lock = Some(seqfile::prepare_seqfile_lock(&opts.sfopts.seqfile, true)?);
        sf = Some(seqfile::SeqFile::open(
            &opts.sfopts.seqfile,
            lock.as_mut().unwrap(),
        )?);
    };

    let u8params: Vec<Vec<u8>> = opts
        .params
        .iter()
        .map(|x| Vec::from(x.as_bytes()))
        .collect();

    let mut envtosend: Vec<(Vec<u8>, Vec<u8>)> = vec![];
    for (key, val) in std::env::vars_os() {
        if key.as_bytes().starts_with(ENV_PREFIX) {
            envtosend.push((
                Vec::from(&key.as_bytes()[ENV_PREFIX.len()..]),
                Vec::from(val.as_bytes()),
            ));
        }
    }

    let meta = FSMetaV1 {
        seq: match sf {
            None => 1,
            Some(ref mut x) => x.increment()?,
        },
        cmd_type: CommandTypeV1::Command(CommandDataV1 {
            params: u8params,
            env: envtosend,
        }),
        ..FSMetaV1::default()
    };

    // Release the lock.  It's already incremented, so why not?
    std::mem::drop(sf);
    std::mem::drop(lock);

    let mut fd_stdout = std::io::stdout();

    if let Some(inpath) = &opts.input {
        if inpath == "-" {
            let mut fd_stdin = std::io::stdin();
            jobfile::write_jobfile_header_payload(meta, &mut fd_stdin, &mut fd_stdout)?;
        } else {
            let mut fd_input = std::fs::File::open(inpath)?;
            jobfile::write_jobfile_header_payload(meta, &mut fd_input, &mut fd_stdout)?;
        }
    } else {
        jobfile::write_jobfile_header(meta, &mut fd_stdout)?;
    }

    Ok(())
}
