#
# Custom partitioning classes.
#
# Copyright (C) 2012-2014  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): Chris Lumens <clumens@redhat.com>
#                    David Lehman <dlehman@redhat.com>
#

# TODO:
# - Deleting an LV is not reflected in available space in the bottom left.
#   - this is only true for preexisting LVs
# - Device descriptions, suggested sizes, etc. should be moved out into a support file.
# - Tabbing behavior in the accordion is weird.
# - Implement striping and mirroring for LVM.
# - Activating reformat should always enable resize for existing devices.

from pykickstart.constants import CLEARPART_TYPE_NONE

from pyanaconda.i18n import _, N_, CP_
from pyanaconda.product import productName, productVersion, translated_new_install_name
from pyanaconda.threads import AnacondaThread, threadMgr
from pyanaconda.constants import THREAD_EXECUTE_STORAGE, THREAD_STORAGE, THREAD_CUSTOM_STORAGE_INIT
from pyanaconda.constants import SIZE_UNITS_DEFAULT
from pyanaconda.iutil import lowerASCII
from pyanaconda.bootloader import BootLoaderError
from pyanaconda.kickstart import refreshAutoSwapSize
from pyanaconda import isys

from blivet import devicefactory
from blivet.formats import device_formats
from blivet.formats import getFormat
from blivet.formats.fs import FS
from blivet.size import Size
from
 blivet.devicefactory import DEVICE_TYPE_LVM
from blivet.devicefactory import DEVICE_TYPE_BTRFS
from blivet.devicefactory import DEVICE_
TYPE_PARTITION
from blivet.devicefactory import DEVICE_TYPE_MD
from blivet.devicefactory import DEVICE_TYPE_DISK
from blivet.devicefactory import DEVICE_TYPE_LVM_THINP
from blivet.devicefactory import SIZE_POLICY_AUTO
from blivet.osinstall import findExistingInstallations, Root
from blivet.autopart import doAutoPartition
from blivet.errors import StorageError
from blivet.errors import NoDisksError
from blivet.errors import NotEnoughFreeSpaceError
from blivet.devicelibs import raid, crypto
from blivet.devices import LUKSDevice

from pyanaconda.storage_utils import ui_storage_logger, device_type_from_autopart
from pyanaconda.storage_utils import DEVICE_TEXT_PARTITION, DEVICE_TEXT_MAP, DEVICE_TEXT_MD
from pyanaconda.storage_utils import PARTITION_ONLY_FORMAT_TYPES, MOUNTPOINT_DESCRIPTIONS
from pyanaconda.storage_utils import NAMED_DEVICE_TYPES, CONTAINER_DEVICE_TYPES
from pyanaconda.storage_utils import SanityError, SanityWarning, LUKSDeviceWithoutKeyError
from pyanaconda import storage_utils

from pyanaconda.ui.communication import hubQ
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.helpers import StorageChecker
from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog
from pyanaconda.ui.gui.spokes.lib.passphrase import PassphraseDialog
from pyanaconda.ui.gui.spokes.lib.accordion import updateSelectorFromDevice, Accordion, Page, CreateNewPage, UnknownPage
from pyanaconda.ui.gui.spokes.lib.refresh import RefreshDialog
from pyanaconda.ui.gui.spokes.lib.summary import ActionSummaryDialog

from pyanaconda.ui.gui.spokes.lib.custom_storage_helpers import size_from_entry
from pyanaconda.ui.gui.spokes.lib.custom_storage_helpers import validate_label, validate_mountpoint, get_raid_level
from pyanaconda.ui.gui.spokes.lib.custom_storage_helpers import selectedRaidLevel, raidLevelSelection, defaultRaidLevel, requiresRaidSelection, containerRaidLevelsSupported, raidLevelsSupported, defaultContainerRaidLevel
from pyanaconda.ui.gui.spokes.lib.custom_storage_helpers import get_container_type, RAID_NOT_ENOUGH_DISKS
from pyanaconda.ui.gui.spokes.lib.custom_storage_helpers import AddDialog, ConfirmDeleteDialog, DisksDialog, ContainerDialog, HelpDialog

from pyanaconda.ui.gui.utils import setViewportBackground, fancy_set_sensitive, ignoreEscape
from pyanaconda.ui.gui.utils import really_hide, really_show, timed_action
from pyanaconda.ui.categories.system import SystemCategory

from gi.repository import Gdk, Gtk
from gi.repository.AnacondaWidgets import MountpointSelector

from functools import wraps

import logging
log = logging.getLogger("anaconda")

__all__ = ["CustomPartitioningSpoke"]

NOTEBOOK_LABEL_PAGE = 0
NOTEBOOK_DETAILS_PAGE = 1
NOTEBOOK_LUKS_PAGE = 2
NOTEBOOK_UNEDITABLE_PAGE = 3
NOTEBOOK_INCOMPLETE_PAGE = 4

NEW_CONTAINER_TEXT = N_("Create a new %(container_type)s ...")
CONTAINER_TOOLTIP = N_("Create or select %(container_type)s")

DEVICE_CONFIGURATION_ERROR_MSG = N_("Device reconfiguration failed. <a href=\"\">Click for "
                                    "details.</a>")
UNRECOVERABLE_ERROR_MSG = N_("Storage configuration reset due to unrecoverable "
                             "error. <a href=\"\">Click for details.</a>")

def dev_type_from_const(dev_type_const):
    """ Return integer corresponding to name for device type defined as
        a constant in blivet.devicefactory.

        :param str dev_type_const: the name of a DEVICE_TYPE_*
        :returns: the corresponding integer code, if there is one
        :rtype: int or NoneType
    """
    return getattr(devicefactory, dev_type_const, None)

def ui_storage_logged(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        with ui_storage_logger():
            return func(*args, **kwargs)

    return decorated

class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
    builderObjects = ["customStorageWindow", "containerStore", "deviceTypeStore",
                      "partitionStore", "raidStoreFiltered", "raidLevelStore",
                      "addImage", "removeImage", "settingsImage",
                      "mountPointCompletion", "mountPointStore"]
    mainWidgetName = "customStorageWindow"
    uiFile = "spokes/custom.glade"
    helpFile = "CustomSpoke.xml"

    category = SystemCategory
    title = N_("MANUAL PARTITIONING")

    # The maximum number of places to show when displaying a size
    MAX_SIZE_PLACES = 2

    # If the user enters a smaller size, the GUI changes it to this value
    MIN_SIZE_ENTRY = Size("1 MiB")

    def __init__(self, data, storage, payload, instclass):
        StorageChecker.__init__(self, min_ram=isys.MIN_GUI_RAM)
        NormalSpoke.__init__(self, data, storage, payload, instclass)

        self._back_already_clicked = False
        self._storage_playground = None

        self.passphrase = ""

        self._current_selector = None
        self._devices = []
        self._error = None
        self._hidden_disks = []
        self._fs_types = set()             # set of supported fstypes
        self._free_space = Size(0)

        self._device_disks = []
        self._device_container_name = None
        self._device_container_raid_level = None
        self._device_container_encrypted = False
        self._device_container_size = SIZE_POLICY_AUTO
        self._device_name_dict = {DEVICE_TYPE_LVM: None,
                                  DEVICE_TYPE_MD: None,
                                  DEVICE_TYPE_LVM_THINP: None,
                                  DEVICE_TYPE_PARTITION: "",
                                  DEVICE_TYPE_BTRFS: "",
                                  DEVICE_TYPE_DISK: ""}

        self._initialized = False

    def apply(self):
        self.clear_errors()

        self._unhide_unusable_disks()

        new_swaps = (dev for dev in self.get_new_devices() if dev.fo