# Copyright (C) 2011, 2014, 2015, 2018 David Maxwell
#
# This file is part of PISM.
#
# PISM 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.
#
# PISM 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 PISM; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

"""Helper functions to make working with the PISM/PETSc option system more pythonic."""

import PISM

def _to_tuple(option, use_default):
    """Convert a PISM Option object into a tuple of (value, flag). Return
    (None, False) if use_default is False and the option was not set.

    """
    if option.is_set() or use_default:
        return (option.value(), option.is_set())

    return (None, False)


def optionsIntWasSet(option, text, default=None):
    """Determines if an integer-valued command-line option was set.

    :param option:  Name of command-line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """

    if default is None:
        return _to_tuple(PISM.cpp.OptionInteger(option, text, 0), False)
    else:
        return _to_tuple(PISM.cpp.OptionInteger(option, text, default), True)


def optionsInt(*args, **kwargs):
    """Same as :func:`optionsIntWasSet` but only returns the integer value."""
    return optionsIntWasSet(*args, **kwargs)[0]


def optionsRealWasSet(option, text, default=None):
    """Determines if a real-valued command line option was set.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionReal(option, text, 0.0), False)
    else:
        return _to_tuple(PISM.cpp.OptionReal(option, text, default), True)


def optionsReal(*args, **kwargs):
    """Same as :func:`optionsRealWasSet` but only returns the real value."""
    return optionsRealWasSet(*args, **kwargs)[0]


def optionsStringWasSet(option, text, default=None):
    """Determines if a string-valued command line option was set.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionString(option, text, ""), False)
    else:
        return _to_tuple(PISM.cpp.OptionString(option, text, default), True)


def optionsString(*args, **kwargs):
    """Same as :func:`optionsStringWasSet` but only returns the string value."""
    return optionsStringWasSet(*args, **kwargs)[0]


def optionsIntArrayWasSet(option, text, default=None):
    """Determines if an integer-array-valued command line option was set.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionIntegerList(option, text, []), False)
    else:
        option = PISM.cpp.OptionIntegerList(option, text, [])
        if option.is_set():
            return _to_tuple(option, True)
        else:
            return (default, False)


def optionsIntArray(*args, **kwargs):
    """Same as :func:`optionsIntArrayWasSet` but only returns the integer array."""
    return optionsIntArrayWasSet(*args, **kwargs)[0]


def optionsRealArrayWasSet(option, text, default=None):
    """Determines if a real-array-valued command line option was set.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionRealList(option, text, []), False)
    else:
        option = PISM.cpp.OptionRealList(option, text, [])
        if option.is_set():
            return _to_tuple(option, True)
        else:
            return (default, False)


def optionsRealArray(*args, **kwargs):
    """Same as :func:`optionsRealArrayWasSet` but only returns the real array."""
    return optionsRealArrayWasSet(*args, **kwargs)[0]


def optionsStringArrayWasSet(option, text, default=None):
    """Determines if a string-array-valued command line option was set.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value if option was not set.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionStringList(option, text, ""), False)
    else:
        option = PISM.cpp.OptionStringList(option, text, default)
        if option.is_set():
            return _to_tuple(option, True)
        else:
            return (default, False)


def optionsStringArray(*args, **kwargs):
    """Same as :func:`optionsStringArrayWasSet` but only returns the string array."""
    return optionsStringArrayWasSet(*args, **kwargs)[0]


def optionsListWasSet(option, text, choices, default):
    """Determines if a string command line option was set, where the string can be one of a few legal options.

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param choices: Comma-separated list of legal values (a string).
    :param default: Default value.
    :returns: Tuple ``(value, wasSet)`` where ``value`` is the value that was set (or the ``default`` value if it was not)
              and ``wasSet`` is a boolean that is ``True`` if the command line option was set explicitly.
    """
    if default is None:
        return _to_tuple(PISM.cpp.OptionKeyword(option, text, choices, ""), False)
    else:
        return _to_tuple(PISM.cpp.OptionKeyword(option, text, choices, default), True)


def optionsList(*args, **kwargs):
    """Same as :func:`optionsListWasSet` but only returns the option value."""
    return optionsListWasSet(*args, **kwargs)[0]


def optionsFlag(option, text, default=False):
    """Determines if a flag command line option  of the  form ``-foo`` or ``-no_foo`` was set.
  The option value is

    :param option:  Name of command line option.
    :param text:    Description of option.
    :param default: Default value.
    :returns: ``True`` if ``-foo`` was set and ``False`` if ``-no_foo`` was set.  If
              neither is set, the `default` is used, and if both are set a :exc:`RuntimeError` is raised.
    """

    if option[0] == '-':
        option = option[1:]
    true_set = PISM.OptionBool("-" + option, text)
    false_set = PISM.OptionBool("-no_" + option, text)

    if true_set and false_set:
        raise RuntimeError("Command line options inconsistent: both -%s and -no_%s are set" % (option, option))

    if true_set:
        return True

    if false_set:
        return False

    return default
