//! A version of the getopt library for use with wide character strings.
//!
//! Note wgetopter expects an mutable array of const strings. It modifies the order of the
//! strings, but not their contents.
/* Declarations for getopt.
   Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc.

This file is part of the GNU C Library.  Its master source is NOT part of
the C library, however.  The master source lives in /gd/gnu/lib.

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

The GNU C Library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

use crate::wchar::prelude::*;

/// Describe how to deal with options that follow non-option ARGV-elements.
///
/// If the caller did not specify anything, the default is PERMUTE.
///
/// REQUIRE_ORDER means don't recognize them as options; stop option processing when the first
/// non-option is seen. This is what Unix does. This mode of operation is selected by using `+'
/// as the first character of the list of option characters.
///
/// PERMUTE is the default.  We permute the contents of ARGV as we scan, so that eventually all
/// the non-options are at the end.  This allows options to be given in any order, even with
/// programs that were not written to expect this.
///
/// RETURN_IN_ORDER is an option available to programs that were written to expect options and
/// other ARGV-elements in any order and that care about the ordering of the two.  We describe
/// each non-option ARGV-element as if it were the argument of an option with character code 1.
/// Using `-` as the first character of the list of option characters selects this mode of
/// operation.
///
/// The special argument `--` forces an end of option-scanning regardless of the value of
/// `ordering`.  In the case of RETURN_IN_ORDER, only `--` can cause `getopt` to return EOF with
/// `woptind` != ARGC.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::upper_case_acronyms)]
enum Ordering {
    REQUIRE_ORDER,
    PERMUTE,
    RETURN_IN_ORDER,
}

/// The special character code, enabled via RETURN_IN_ORDER, indicating a non-option argument.
pub const NONOPTION_CHAR_CODE: char = '\x01';

impl Default for Ordering {
    fn default() -> Self {
        Ordering::PERMUTE
    }
}

fn empty_wstr() -> &'static wstr {
    Default::default()
}

pub struct wgetopter_t<'opts, 'args, 'argarray> {
    /// Argv.
    pub argv: &'argarray mut [&'args wstr],

    /// For communication from `getopt` to the caller. When `getopt` finds an option that takes an
    /// argument, the argument value is returned here. Also, when `ordering` is RETURN_IN_ORDER, each
    /// non-option ARGV-element is returned here.
    pub woptarg: Option<&'args wstr>,

    shortopts: &'opts wstr,
    longopts: &'opts [woption<'opts>],

    /// The next char to be scanned in the option-element in which the last option character we
    /// returned was found. This allows us to pick up the scan where we left off.
    ///
    /// If this is empty, it means resume the scan by advancing to the next ARGV-element.
    pub nextchar: &'args wstr,

    /// Index in ARGV of the next element to be scanned. This is used for communication to and from
    /// the caller and for communication between successive calls to `getopt`.
    ///
    /// On entry to `getopt`, zero means this is the first call; initialize.
    ///
    /// When `getopt` returns EOF, this is the index of the first of the non-option elements that the
    /// caller should itself scan.
    ///
    /// Otherwise, `woptind` communicates from one call to the next how much of ARGV has been scanned
    /// so far.
    // XXX 1003.2 says this must be 1 before any call.
    pub woptind: usize,

    /// Set to an option character which was unrecognized.
    woptopt: char,

    /// Describe how to deal with options that follow non-option ARGV-elements.
    ordering: Ordering,

    /// Handle permutation of arguments.
    ///
    /// Describe the part of ARGV that contains non-options that have been skipped.  `first_nonopt`
    /// is the index in ARGV of the first of them; `last_nonopt` is the index after the last of them.
    pub first_nonopt: usize,
    pub last_nonopt: usize,

    missing_arg_return_colon: bool,
    initialized: bool,
}

/// Names for the values of the `has_arg` field of `woption`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum woption_argument_t {
    no_argument,
    required_argument,
    optional_argument,
}

/// Describe the long-named options requested by the application. The LONG_OPTIONS argument to
/// getopt_long or getopt_long_only is a vector of `struct option' terminated by an element
/// containing a name which is zero.
///
/// The field `has_arg` is:
/// no_argument    (or 0) if the option does not take an argument,
/// required_argument  (or 1) if the option requires an argument,
/// optional_argument   (or 2) if the option takes an optional argument.
///
/// If the field `flag` is not NULL, it points to a variable that is set to the value given in the
/// field `val` when the option is found, but left unchanged if the option is not found.
///
/// To have a long-named option do something other than set an `int` to a compiled-in constant, such
/// as set a value from `optarg`, set the option's `flag` field to zero and its `val` field to a
/// nonzero value (the equivalent single-letter option character, if there is one).  For long
/// options that have a zero `flag` field, `getopt` returns the contents of the `val` field.
#[derive(Debug, Clone, Copy)]
pub struct woption<'a> {
    /// Long name for switch.
    pub name: &'a wstr,

    pub has_arg: woption_argument_t,

    /// If \c flag is non-null, this is the value that flag will be set to. Otherwise, this is the
    /// return-value of the function call.
    pub val: char,
}

/// Helper function to create a woption.
pub const fn wopt(name: &wstr, has_arg: woption_argument_t, val: char) -> woption<'_> {
    woption { name, has_arg, val }
}

impl<'opts, 'args, 'argarray> wgetopter_t<'opts, 'args, 'argarray> {
    pub fn new(
        shortopts: &'opts wstr,
        longopts: &'opts [woption],
        argv: &'argarray mut [&'args wstr],
    ) -> Self {
        return wgetopter_t {
            woptopt: '?',
            argv,
            shortopts,
            longopts,
            first_nonopt: 0,
            initialized: false,
            last_nonopt: 0,
            missing_arg_return_colon: false,
            nextchar: Default::default(),
            ordering: Ordering::PERMUTE,
            woptarg: None,
            woptind: 0,
        };
    }

    pub fn wgetopt_long(&mut self) -> Option<char> {
        assert!(self.woptind <= self.argc(), "woptind is out of range");
        let mut ignored = 0;
        return self._wgetopt_internal(&mut ignored, false);
    }

    pub fn wgetopt_long_idx(&mut self, opt_index: &mut usize) -> Option<char> {
        return self._wgetopt_internal(opt_index, false);
    }

    /// \return the number of arguments.
    fn argc(&self) -> usize {
        return self.argv.len();
    }

    /// Exchange two adjacent subsequences of ARGV. One subsequence is elements
    /// [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The
    /// other is elements [last_nonopt,woptind), which contains all the options processed since those
    /// non-options were skipped.
    ///
    /// `first_nonopt` and `last_nonopt` are relocated so that they describe the new indices of the
    /// non-options in ARGV after they are moved.
    fn exchange(&mut self) {
        let mut bottom = self.first_nonopt;
        let middle = self.last_nonopt;
        let mut top = self.woptind;

        // Exchange the shorter segment with the far end of the longer segment. That puts the shorter
        // segment into the right place. It leaves the longer segment in the right place overall, but it
        // consists of two parts that need to be swapped next.
        while top > middle && middle > bottom {
            if top - middle > middle - bottom {
                // Bottom segment is the short one.
                let len = middle - bottom;

                // Swap it with the top part of the top segment.
                for i in 0..len {
                    self.argv.swap(bottom + i, top - (middle - bottom) + i);
                }
                // Exclude the moved bottom segment from further swapping.
                top -= len;
            } else {
                // Top segment is the short one.
                let len = top - middle;

                // Swap it with the bottom part of the bottom segment.
                for i in 0..len {
                    self.argv.swap(bottom + i, middle + i);
                }
                // Exclude the moved top segment from further swapping.
                bottom += len;
            }
        }

        // Update records for the slots the non-options now occupy.
        self.first_nonopt += self.woptind - self.last_nonopt;
        self.last_nonopt = self.woptind;
    }

    /// Initialize the internal data when the first call is made.
    fn _wgetopt_initialize(&mut self) {
        // Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the
        // sequence of previously skipped non-option ARGV-elements is empty.
        self.first_nonopt = 1;
        self.last_nonopt = 1;
        self.woptind = 1;
        self.nextchar = empty_wstr();

        let mut optstring = self.shortopts;

        // Determine how to handle the ordering of options and nonoptions.
        if optstring.char_at(0) == '-' {
            self.ordering = Ordering::RETURN_IN_ORDER;
            optstring = &optstring[1..];
        } else if optstring.char_at(0) == '+' {
            self.ordering = Ordering::REQUIRE_ORDER;
            optstring = &optstring[1..];
        } else {
            self.ordering = Ordering::PERMUTE;
        }

        if optstring.char_at(0) == ':' {
            self.missing_arg_return_colon = true;
            optstring = &optstring[1..];
        }

        self.shortopts = optstring;
        self.initialized = true;
    }

    /// Advance to the next ARGV-element.
    /// \return Some(\0) on success, or None or another value if we should stop.
    fn _advance_to_next_argv(&mut self) -> Option<char> {
        let argc = self.argc();
        if self.ordering == Ordering::PERMUTE {
            // If we have just processed some options following some non-options, exchange them so
            // that the options come first.
            if self.first_nonopt != self.last_nonopt && self.last_nonopt != self.woptind {
                self.exchange();
            } else if self.last_nonopt != self.woptind {
                self.first_nonopt = self.woptind;
            }

            // Skip any additional non-options and extend the range of non-options previously
            // skipped.
            while self.woptind < argc
                && (self.argv[self.woptind].char_at(0) != '-' || self.argv[self.woptind].len() == 1)
            {
                self.woptind += 1;
            }
            self.last_nonopt = self.woptind;
        }

        // The special ARGV-element `--' means premature end of options. Skip it like a null option,
        // then exchange with previous non-options as if it were an option, then skip everything
        // else like a non-option.
        if self.woptind != argc && self.argv[self.woptind] == "--" {
            self.woptind += 1;

            if self.first_nonopt != self.last_nonopt && self.last_nonopt != self.woptind {
                self.exchange();
            } else if self.first_nonopt == self.last_nonopt {
                self.first_nonopt = self.woptind;
            }
            self.last_nonopt = argc;
            self.woptind = argc;
        }

        // If we have done all the ARGV-elements, stop the scan and back over any non-options that
        // we skipped and permuted.

        if self.woptind == argc {
            // Set the next-arg-index to point at the non-options that we previously skipped, so the
            // caller will digest them.
            if self.first_nonopt != self.last_nonopt {
                self.woptind = self.first_nonopt;
            }
            return None;
        }

        // If we have come to a non-option and did not permute it, either stop the scan or describe
        // it to the caller and pass it by.
        if self.argv[self.woptind].char_at(0) != '-' || self.argv[self.woptind].len() == 1 {
            if self.ordering == Ordering::REQUIRE_ORDER {
                return None;
            }
            self.woptarg = Some(self.argv[self.woptind]);
            self.woptind += 1;
            return Some(NONOPTION_CHAR_CODE);
        }

        // We have found another option-ARGV-element. Skip the initial punctuation.
        let skip = if !self.longopts.is_empty() && self.argv[self.woptind].char_at(1) == '-' {
            2
        } else {
            1
        };
        self.nextchar = self.argv[self.woptind][skip..].into();
        return Some(char::from(0));
    }

    /// Check for a matching short opt.
    fn _handle_short_opt(&mut self) -> char {
        // Look at and handle the next short option-character.
        let mut c = self.nextchar.char_at(0);
        self.nextchar = &self.nextchar[1..];

        let temp = match self.shortopts.chars().position(|sc| sc == c) {
            Some(pos) => &self.shortopts[pos..],
            None => L!(""),
        };

        // Increment `woptind' when we start to process its last character.
        if self.nextchar.is_empty() {
            self.woptind += 1;
        }

        if temp.is_empty() || c == ':' {
            self.woptopt = c;

            if !self.nextchar.is_empty() {
                self.woptind += 1;
            }
            return '?';
        }

        if temp.char_at(1) != ':' {
            return c;
        }

        if temp.char_at(2) == ':' {
            // This is an option that accepts an argument optionally.
            if !self.nextchar.is_empty() {
                self.woptarg = Some(self.nextchar);
                self.woptind += 1;
            } else {
                self.woptarg = None;
            }
            self.nextchar = empty_wstr();
        } else {
            // This is an option that requires an argument.
            if !self.nextchar.is_empty() {
                self.woptarg = Some(self.nextchar);
                // If we end this ARGV-element by taking the rest as an arg, we must advance to
                // the next element now.
                self.woptind += 1;
            } else if self.woptind == self.argc() {
                self.woptopt = c;
                c = if self.missing_arg_return_colon {
                    ':'
                } else {
                    '?'
                };
            } else {
                // We already incremented `woptind' once; increment it again when taking next
                // ARGV-elt as argument.
                self.woptarg = Some(self.argv[self.woptind]);
                self.woptind += 1;
            }
            self.nextchar = empty_wstr();
        }

        return c;
    }

    fn _update_long_opt(
        &mut self,
        pfound: &woption,
        nameend: usize,
        longind: &mut usize,
        option_index: usize,
        retval: &mut char,
    ) {
        self.woptind += 1;
        assert!(self.nextchar.char_at(nameend) == '\0' || self.nextchar.char_at(nameend) == '=');
        if self.nextchar.char_at(nameend) == '=' {
            if pfound.has_arg != woption_argument_t::no_argument {
                self.woptarg = Some(self.nextchar[(nameend + 1)..].into());
            } else {
                self.nextchar = empty_wstr();
                *retval = '?';
                return;
            }
        } else if pfound.has_arg == woption_argument_t::required_argument {
            if self.woptind < self.argc() {
                self.woptarg = Some(self.argv[self.woptind]);
                self.woptind += 1;
            } else {
                self.nextchar = empty_wstr();
                *retval = if self.missing_arg_return_colon {
                    ':'
                } else {
                    '?'
                };
                return;
            }
        }

        self.nextchar = empty_wstr();
        *longind = option_index;
        *retval = pfound.val;
    }

    /// Find a matching long opt.
    fn _find_matching_long_opt(
        &self,
        nameend: usize,
        exact: &mut bool,
        ambig: &mut bool,
        indfound: &mut usize,
    ) -> Option<woption<'opts>> {
        let mut pfound: Option<woption> = None;

        // Test all long options for either exact match or abbreviated matches.
        for (option_index, p) in self.longopts.iter().enumerate() {
            // Check if current option is prefix of long opt
            if p.name.starts_with(&self.nextchar[..nameend]) {
                if nameend == p.name.len() {
                    // The current option is exact match of this long option
                    pfound = Some(*p);
                    *indfound = option_index;
                    *exact = true;
                    break;
                } else if pfound.is_none() {
                    // current option is first prefix match but not exact match
                    pfound = Some(*p);
                    *indfound = option_index;
                } else {
                    // current option is second or later prefix match but not exact match
                    *ambig = true;
                }
            }
        }
        return pfound;
    }

    /// Check for a matching long opt.
    fn _handle_long_opt(
        &mut self,
        longind: &mut usize,
        long_only: bool,
        retval: &mut char,
    ) -> bool {
        let mut exact = false;
        let mut ambig = false;
        let mut indfound: usize = 0;

        let mut nameend = 0;
        while self.nextchar.char_at(nameend) != '\0' && self.nextchar.char_at(nameend) != '=' {
            nameend += 1;
        }

        let pfound = self._find_matching_long_opt(nameend, &mut exact, &mut ambig, &mut indfound);

        if ambig && !exact {
            self.nextchar = empty_wstr();
            self.woptind += 1;
            *retval = '?';
            return true;
        }

        if let Some(pfound) = pfound {
            self._update_long_opt(&pfound, nameend, longind, indfound, retval);
            return true;
        }

        // Can't find it as a long option.  If this is not getopt_long_only, or the option starts
        // with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a
        // short option.
        if !long_only
            || self.argv[self.woptind].char_at(1) == '-'
            || !self
                .shortopts
                .as_char_slice()
                .contains(&self.nextchar.char_at(0))
        {
            self.nextchar = empty_wstr();
            self.woptind += 1;
            *retval = '?';
            return true;
        }

        return false;
    }

    /// Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING.
    ///
    /// If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option
    /// element.  The characters of this element (aside from the initial '-') are option characters.  If
    /// `getopt` is called repeatedly, it returns successively each of the option characters from each of
    /// the option elements.
    ///
    /// If `getopt` finds another option character, it returns that character, updating `woptind` and
    /// `nextchar` so that the next call to `getopt` can resume the scan with the following option
    /// character or ARGV-element.
    ///
    /// If there are no more option characters, `getopt` returns `EOF`. Then `woptind` is the index in
    /// ARGV of the first ARGV-element that is not an option.  (The ARGV-elements have been permuted so
    /// that those that are not options now come last.)
    ///
    /// OPTSTRING is a string containing the legitimate option characters. If an option character is seen
    /// that is not listed in OPTSTRING, return '?'.
    ///
    /// If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text
    /// in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg`.
    /// Two colons mean an option that wants an optional arg; if there is text in the current
    /// ARGV-element, it is returned in `w.woptarg`, otherwise `w.woptarg` is set to zero.
    ///
    /// If OPTSTRING starts with `-` or `+', it requests different methods of handling the non-option
    /// ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
    ///
    /// Long-named options begin with `--` instead of `-`. Their names may be abbreviated as long as the
    /// abbreviation is unique or is an exact match for some defined option.  If they have an argument,
    /// it follows the option name in the same ARGV-element, separated from the option name by a `=', or
    /// else the in next ARGV-element. When `getopt` finds a long-named option, it returns 0 if that
    /// option's `flag` field is nonzero, the value of the option's `val` field if the `flag` field is
    /// zero.
    ///
    /// LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero.
    ///
    /// LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a
    /// long-named option has been found by the most recent call.
    ///
    /// If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options.
    fn _wgetopt_internal(&mut self, longind: &mut usize, long_only: bool) -> Option<char> {
        if !self.initialized {
            self._wgetopt_initialize();
        }
        self.woptarg = None;

        if self.nextchar.is_empty() {
            let narg = self._advance_to_next_argv();
            if narg != Some(char::from(0)) {
                return narg;
            }
        }

        // Decode the current option-ARGV-element.

        // Check whether the ARGV-element is a long option.
        //
        // If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't
        // consider it an abbreviated form of a long option that starts with f.  Otherwise there would
        // be no way to give the -f short option.
        //
        // On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do
        // consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg
        // "u".
        //
        // This distinction seems to be the most useful approach.
        if !self.longopts.is_empty() && self.woptind < self.argc() {
            let arg = self.argv[self.woptind];

            #[allow(clippy::if_same_then_else)]
            #[allow(clippy::needless_bool)]
            let try_long = if arg.char_at(0) == '-' && arg.char_at(1) == '-' {
                // Like --foo
                true
            } else if long_only && arg.len() >= 3 {
                // Like -fu
                true
            } else if !self.shortopts.as_char_slice().contains(&arg.char_at(1)) {
                // Like -f, but f is not a short arg.
                true
            } else {
                false
            };

            if try_long {
                let mut retval = '\0';
                if self._handle_long_opt(longind, long_only, &mut retval) {
                    return Some(retval);
                }
            }
        }

        return Some(self._handle_short_opt());
    }
}
