# encoding: utf-8
#
# Copyright 2009-2017 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
updatecheck.installationstate

Created by Greg Neagle on 2017-01-01.

Utilities for determining installation status for Munki items.
"""

import os

from . import catalogs
from . import compare

from .. import display
from .. import osutils
from .. import profiles
from .. import scriptutils
from .. import utils


def installed_state(item_pl):
    """Checks to see if the item described by item_pl (or a newer version) is
    currently installed

    All tests must pass to be considered installed.
    Returns 1 if it looks like this version is installed
    Returns 2 if it looks like a newer version is installed.
    Returns 0 otherwise.
    """
    foundnewer = False

    if item_pl.get('OnDemand'):
        # always install these items -- retcode 0 means install is needed
        display.display_debug1('This is an OnDemand item. Must install.')
        return 0

    if item_pl.get('installcheck_script'):
        retcode = scriptutils.run_embedded_script(
            'installcheck_script', item_pl, suppress_error=True)
        display.display_debug1('installcheck_script returned %s', retcode)
        # retcode 0 means install is needed
        if retcode == 0:
            return 0
        # non-zero could be an error or successfully indicating
        # that an install is not needed. We hope it's the latter.
        # return 1 so we're marked as not needing to be installed
        return 1

    # this was deprecated a very long time ago. removing 02 Jan 2017
    #if item_pl.get('softwareupdatename'):
    #    available_apple_updates = appleupdates.softwareUpdateList()
    #    display.display_debug2(
    #        'Available Apple updates:\n%s', available_apple_updates)
    #    if item_pl['softwareupdatename'] in available_apple_updates:
    #        display.display_debug1(
    #            '%s is in available Apple Software Updates',
    #            item_pl['softwareupdatename'])
    #        # return 0 so we're marked as needing to be installed
    #        return 0
    #    else:
    #        display.display_debug1(
    #            '%s is not in available Apple Software Updates',
    #            item_pl['softwareupdatename'])
    #        # return 1 so we're marked as not needing to be installed
    #        return 1

    if item_pl.get('installer_type') == 'startosinstall':
        current_os_vers = osutils.getOsVersion()
        item_os_vers = item_pl.get('version')
        # need just major.minor part of the version -- 10.12 and not 10.12.4
        item_os_vers = '.'.join(item_os_vers.split('.')[0:2])
        comparison = compare.compare_versions(current_os_vers, item_os_vers)
        if comparison == compare.VERSION_IS_LOWER:
            return 0
        if comparison == compare.VERSION_IS_HIGHER:
            return 2
        # version is the same
        return 1

    if item_pl.get('installer_type') == 'profile':
        identifier = item_pl.get('PayloadIdentifier')
        hash_value = item_pl.get('installer_item_hash')
        if profiles.profile_needs_to_be_installed(identifier, hash_value):
            return 0
        else:
            return 1

     # does 'installs' exist and is it non-empty?
    if item_pl.get('installs', None):
        installitems = item_pl['installs']
        for item in installitems:
            try:
                comparison = compare.compare_item_version(item)
                if comparison in (-1, 0):
                    return 0
                elif comparison == 2:
                    # this item is newer
                    foundnewer = True
            except utils.Error, errmsg:
                # some problem with the installs data
                display.display_error(unicode(errmsg))
                # return 1 so we're marked as not needing to be installed
                return 1

    # if there is no 'installs' key, then we'll use receipt info
    # to determine install status.
    elif 'receipts' in item_pl:
        receipts = item_pl['receipts']
        for item in receipts:
            try:
                comparison = compare.compare_receipt_version(item)
                if comparison in (-1, 0):
                    # not there or older
                    return 0
                elif comparison == 2:
                    foundnewer = True
            except utils.Error, errmsg:
                # some problem with the receipts data
                display.display_error(unicode(errmsg))
                # return 1 so we're marked as not needing to be installed
                return 1

    # if we got this far, we passed all the tests, so the item
    # must be installed (or we don't have enough info...)
    if foundnewer:
        return 2
    else:
        return 1


def some_version_installed(item_pl):
    """Checks to see if some version of an item is installed.

    Args:
      item_pl: item plist for the item to check for version of.

    Returns a boolean.
    """
    if item_pl.get('OnDemand'):
        # These should never be counted as installed
        display.display_debug1('This is an OnDemand item.')
        return False

    if item_pl.get('installcheck_script'):
        retcode = scriptutils.run_embedded_script(
            'installcheck_script', item_pl, suppress_error=True)
        display.display_debug1(
            'installcheck_script returned %s', retcode)
        # retcode 0 means install is needed
        # (ie, item is not installed)
        if retcode == 0:
            return False
        # non-zero could be an error or successfully indicating
        # that an install is not needed. We hope it's the latter.
        return True

    if item_pl.get('installer_type') == 'startosinstall':
        # Some version of macOS is always installed!
        return True

    if item_pl.get('installer_type') == 'profile':
        identifier = item_pl.get('PayloadIdentifier')
        return profiles.profile_is_installed(identifier)

    # does 'installs' exist and is it non-empty?
    if item_pl.get('installs'):
        installitems = item_pl['installs']
        # check each item for existence
        for item in installitems:
            try:
                if compare.compare_item_version(item) == 0:
                    # not there
                    return False
            except utils.Error, errmsg:
                # some problem with the installs data
                display.display_error(unicode(errmsg))
                return False

    # if there is no 'installs' key, then we'll use receipt info
    # to determine install status.
    elif 'receipts' in item_pl:
        receipts = item_pl['receipts']
        for item in receipts:
            try:
                if compare.compare_receipt_version(item) == 0:
                    # not there
                    return False
            except utils.Error, errmsg:
                # some problem with the installs data
                display.display_error(unicode(errmsg))
                return False

    # if we got this far, we passed all the tests, so the item
    # must be installed (or we don't have enough info...)
    return True


def evidence_this_is_installed(item_pl):
    """Checks to see if there is evidence that the item described by item_pl
    (any version) is currently installed.

    If any tests pass, the item might be installed.
    This is used when determining if we can remove the item, thus
    the attention given to the uninstall method.

    Returns a boolean.
    """
    if item_pl.get('OnDemand'):
        # These should never be counted as installed
        display.display_debug1('This is an OnDemand item.')
        return False

    if item_pl.get('uninstallcheck_script'):
        retcode = scriptutils.run_embedded_script(
            'uninstallcheck_script', item_pl, suppress_error=True)
        display.display_debug1(
            'uninstallcheck_script returned %s', retcode)
        # retcode 0 means uninstall is needed
        # (ie, item is installed)
        if retcode == 0:
            return True
        # non-zero could be an error or successfully indicating
        # that an uninstall is not needed
        return False

    if item_pl.get('installcheck_script'):
        retcode = scriptutils.run_embedded_script(
            'installcheck_script', item_pl, suppress_error=True)
        display.display_debug1(
            'installcheck_script returned %s', retcode)
        # retcode 0 means install is needed
        # (ie, item is not installed)
        if retcode == 0:
            return False
        # non-zero could be an error or successfully indicating
        # that an install is not needed
        return True

    if item_pl.get('installer_type') == 'startosinstall':
        # Some version of macOS is always installed!
        return True

    if item_pl.get('installer_type') == 'profile':
        identifier = item_pl.get('PayloadIdentifier')
        return profiles.profile_is_installed(identifier)

    foundallinstallitems = False
    if ('installs' in item_pl and
            item_pl.get('uninstall_method') != 'removepackages'):
        display.display_debug2("Checking 'installs' items...")
        installitems = item_pl['installs']
        foundallinstallitems = True
        for item in installitems:
            if 'path' in item:
                # we can only check by path; if the item has been moved
                # we're not clever enough to find it, and our removal
                # methods are currently even less clever
                if not os.path.exists(item['path']):
                    # this item isn't on disk
                    display.display_debug2(
                        '%s not found on disk.', item['path'])
                    foundallinstallitems = False
        if (foundallinstallitems and
                item_pl.get('uninstall_method') != 'removepackages'):
            return True
    if item_pl.get('receipts'):
        display.display_debug2("Checking receipts...")
        pkgdata = catalogs.analyze_installed_pkgs()
        if item_pl['name'] in pkgdata['installed_names']:
            return True
        else:
            display.display_debug2("Installed receipts don't match.")

    # if we got this far, we failed all the tests, so the item
    # must not be installed (or we dont't have the right info...)
    return False


if __name__ == '__main__':
    print 'This is a library of support tools for the Munki Suite.'
