# file openpyxl/style.py

# Copyright (c) 2010-2011 openpyxl
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# @license: http://www.opensource.org/licenses/mit-license.php
# @author: see AUTHORS file

"""Style and formatting option tracking."""

# Python stdlib imports
import re
try:
    from hashlib import md5
except ImportError:
    from md5 import md5

from copy import deepcopy
from openpyxl.shared.compat import any

class HashableObject(object):
    """Define how to hash property classes."""
    __fields__ = None
    __leaf__ = False

    def __repr__(self):

        return ':'.join([repr(getattr(self, x)) for x in self.__fields__])

    def __hash__(self):

#        return int(md5(repr(self)).hexdigest(), 16)
        return hash(repr(self))

class Color(HashableObject):
    """Named colors for use in styles."""
    BLACK = 'FF000000'
    WHITE = 'FFFFFFFF'
    RED = 'FFFF0000'
    DARKRED = 'FF800000'
    BLUE = 'FF0000FF'
    DARKBLUE = 'FF000080'
    GREEN = 'FF00FF00'
    DARKGREEN = 'FF008000'
    YELLOW = 'FFFFFF00'
    DARKYELLOW = 'FF808000'

    __fields__ = ('index',)
    __slots__ = __fields__
    __leaf__ = True

    def __init__(self, index):
        super(Color, self).__init__()
        self.index = index


class Font(HashableObject):
    """Font options used in styles."""
    UNDERLINE_NONE = 'none'
    UNDERLINE_DOUBLE = 'double'
    UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting'
    UNDERLINE_SINGLE = 'single'
    UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting'

    __fields__ = ('name',
                  'size',
                  'bold',
                  'italic',
                  'superscript',
                  'subscript',
                  'underline',
                  'strikethrough',
                  'color')
    __slots__ = __fields__

    def __init__(self):
        super(Font, self).__init__()
        self.name = 'Calibri'
        self.size = 11
        self.bold = False
        self.italic = False
        self.superscript = False
        self.subscript = False
        self.underline = self.UNDERLINE_NONE
        self.strikethrough = False
        self.color = Color(Color.BLACK)


class Fill(HashableObject):
    """Area fill patterns for use in styles."""
    FILL_NONE = 'none'
    FILL_SOLID = 'solid'
    FILL_GRADIENT_LINEAR = 'linear'
    FILL_GRADIENT_PATH = 'path'
    FILL_PATTERN_DARKDOWN = 'darkDown'
    FILL_PATTERN_DARKGRAY = 'darkGray'
    FILL_PATTERN_DARKGRID = 'darkGrid'
    FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'
    FILL_PATTERN_DARKTRELLIS = 'darkTrellis'
    FILL_PATTERN_DARKUP = 'darkUp'
    FILL_PATTERN_DARKVERTICAL = 'darkVertical'
    FILL_PATTERN_GRAY0625 = 'gray0625'
    FILL_PATTERN_GRAY125 = 'gray125'
    FILL_PATTERN_LIGHTDOWN = 'lightDown'
    FILL_PATTERN_LIGHTGRAY = 'lightGray'
    FILL_PATTERN_LIGHTGRID = 'lightGrid'
    FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'
    FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'
    FILL_PATTERN_LIGHTUP = 'lightUp'
    FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'
    FILL_PATTERN_MEDIUMGRAY = 'mediumGray'

    __fields__ = ('fill_type',
                  'rotation',
                  'start_color',
                  'end_color')
    __slots__ = __fields__

    def __init__(self):
        super(Fill, self).__init__()
        self.fill_type = self.FILL_NONE
        self.rotation = 0
        self.start_color = Color(Color.WHITE)
        self.end_color = Color(Color.BLACK)


class Border(HashableObject):
    """Border options for use in styles."""
    BORDER_NONE = 'none'
    BORDER_DASHDOT = 'dashDot'
    BORDER_DASHDOTDOT = 'dashDotDot'
    BORDER_DASHED = 'dashed'
    BORDER_DOTTED = 'dotted'
    BORDER_DOUBLE = 'double'
    BORDER_HAIR = 'hair'
    BORDER_MEDIUM = 'medium'
    BORDER_MEDIUMDASHDOT = 'mediumDashDot'
    BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot'
    BORDER_MEDIUMDASHED = 'mediumDashed'
    BORDER_SLANTDASHDOT = 'slantDashDot'
    BORDER_THICK = 'thick'
    BORDER_THIN = 'thin'

    __fields__ = ('border_style',
                  'color')
    __slots__ = __fields__

    def __init__(self):
        super(Border, self).__init__()
        self.border_style = self.BORDER_NONE
        self.color = Color(Color.BLACK)


class Borders(HashableObject):
    """Border positioning for use in styles."""
    DIAGONAL_NONE = 0
    DIAGONAL_UP = 1
    DIAGONAL_DOWN = 2
    DIAGONAL_BOTH = 3

    __fields__ = ('left',
                  'right',
                  'top',
                  'bottom',
                  'diagonal',
                  'diagonal_direction',
                  'all_borders',
                  'outline',
                  'inside',
                  'vertical',
                  'horizontal')
    __slots__ = __fields__

    def __init__(self):
        super(Borders, self).__init__()
        self.left = Border()
        self.right = Border()
        self.top = Border()
        self.bottom = Border()
        self.diagonal = Border()
        self.diagonal_direction = self.DIAGONAL_NONE

        self.all_borders = Border()
        self.outline = Border()
        self.inside = Border()
        self.vertical = Border()
        self.horizontal = Border()


class Alignment(HashableObject):
    """Alignment options for use in styles."""
    HORIZONTAL_GENERAL = 'general'
    HORIZONTAL_LEFT = 'left'
    HORIZONTAL_RIGHT = 'right'
    HORIZONTAL_CENTER = 'center'
    HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous'
    HORIZONTAL_JUSTIFY = 'justify'
    VERTICAL_BOTTOM = 'bottom'
    VERTICAL_TOP = 'top'
    VERTICAL_CENTER = 'center'
    VERTICAL_JUSTIFY = 'justify'

    __fields__ = ('horizontal',
                  'vertical',
                  'text_rotation',
                  'wrap_text',
                  'shrink_to_fit',
                  'indent')
    __slots__ = __fields__
    __leaf__ = True

    def __init__(self):
        super(Alignment, self).__init__()
        self.horizontal = self.HORIZONTAL_GENERAL
        self.vertical = self.VERTICAL_BOTTOM
        self.text_rotation = 0
        self.wrap_text = False
        self.shrink_to_fit = False
        self.indent = 0


class NumberFormat(HashableObject):
    """Numer formatting for use in styles."""
    FORMAT_GENERAL = 'General'
    FORMAT_TEXT = '@'
    FORMAT_NUMBER = '0'
    FORMAT_NUMBER_00 = '0.00'
    FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00'
    FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'
    FORMAT_PERCENTAGE = '0%'
    FORMAT_PERCENTAGE_00 = '0.00%'
    FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'
    FORMAT_DATE_YYYYMMDD = 'yy-mm-dd'
    FORMAT_DATE_DDMMYYYY = 'dd/mm/yy'
    FORMAT_DATE_DMYSLASH = 'd/m/y'
    FORMAT_DATE_DMYMINUS = 'd-m-y'
    FORMAT_DATE_DMMINUS = 'd-m'
    FORMAT_DATE_MYMINUS = 'm-y'
    FORMAT_DATE_XLSX14 = 'mm-dd-yy'
    FORMAT_DATE_XLSX15 = 'd-mmm-yy'
    FORMAT_DATE_XLSX16 = 'd-mmm'
    FORMAT_DATE_XLSX17 = 'mmm-yy'
    FORMAT_DATE_XLSX22 = 'm/d/yy h:mm'
    FORMAT_DATE_DATETIME = 'd/m/y h:mm'
    FORMAT_DATE_TIME1 = 'h:mm AM/PM'
    FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM'
    FORMAT_DATE_TIME3 = 'h:mm'
    FORMAT_DATE_TIME4 = 'h:mm:ss'
    FORMAT_DATE_TIME5 = 'mm:ss'
    FORMAT_DATE_TIME6 = 'h:mm:ss'
    FORMAT_DATE_TIME7 = 'i:s.S'
    FORMAT_DATE_TIME8 = 'h:mm:ss@'
    FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss'
    FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd@'
    FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-'
    FORMAT_CURRENCY_USD = '$#,##0_-'
    FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-'
    _BUILTIN_FORMATS = {
        0: 'General',
        1: '0',
        2: '0.00',
        3: '#,##0',
        4: '#,##0.00',

        9: '0%',
        10: '0.00%',
        11: '0.00E+00',
        12: '# ?/?',
        13: '# ??/??',
        14: 'mm-dd-yy',
        15: 'd-mmm-yy',
        16: 'd-mmm',
        17: 'mmm-yy',
        18: 'h:mm AM/PM',
        19: 'h:mm:ss AM/PM',
        20: 'h:mm',
        21: 'h:mm:ss',
        22: 'm/d/yy h:mm',

        37: '#,##0 (#,##0)',
        38: '#,##0 [Red](#,##0)',
        39: '#,##0.00(#,##0.00)',
        40: '#,##0.00[Red](#,##0.00)',

        41: '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
        42: '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
        43: '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',

        44: '_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)',
        45: 'mm:ss',
        46: '[h]:mm:ss',
        47: 'mmss.0',
        48: '##0.0E+0',
        49: '@', }
    _BUILTIN_FORMATS_REVERSE = dict(
            [(value, key) for key, value in _BUILTIN_FORMATS.items()])

    __fields__ = ('_format_code',
                  '_format_index')
    __slots__ = __fields__
    __leaf__ = True

    DATE_INDICATORS = 'dmyhs'
    BAD_DATE_RE = re.compile(r'(\[|").*[dmhys].*(\]|")')

    #def __eq__(self, other):
        #return self.format_code == other.format_code

    #def __ne__(self, other):
        #return self.format_code != other.format_code

    #def __hash__(self):
        #return super(NumberFormat, self).__hash__()

    def __init__(self):
        super(NumberFormat, self).__init__()
        self._format_code = self.FORMAT_GENERAL
        self._format_index = 0

    def _set_format_code(self, format_code = FORMAT_GENERAL):
        """Setter for the format_code property."""
        self._format_code = format_code
        self._format_index = self.builtin_format_id(format = format_code)

    def _get_format_code(self):
        """Getter for the format_code property."""
        return self._format_code

    format_code = property(_get_format_code, _set_format_code)

    def builtin_format_code(self, index):
        """Return one of the standard format codes by index."""
        return self._BUILTIN_FORMATS[index]

    def is_builtin(self, format = None):
        """Check if a format code is a standard format code."""
        if format is None:
            format = self._format_code
        return format in self._BUILTIN_FORMATS.values()

    def builtin_format_id(self, format):
        """Return the id of a standard style."""
        return self._BUILTIN_FORMATS_REVERSE.get(format, None)

    def is_date_format(self, format = None):
        """Check if the number format is actually representing a date."""
        if format is None:
            format = self._format_code

        if any([x in format for x in self.DATE_INDICATORS]):
            if self.BAD_DATE_RE.search(format) is None:
                return True

        return False

class Protection(HashableObject):
    """Protection options for use in styles."""
    PROTECTION_INHERIT = 'inherit'
    PROTECTION_PROTECTED = 'protected'
    PROTECTION_UNPROTECTED = 'unprotected'

    __fields__ = ('locked',
                  'hidden')
    __slots__ = __fields__
    __leaf__ = True

    def __init__(self):
        super(Protection, self).__init__()
        self.locked = self.PROTECTION_INHERIT
        self.hidden = self.PROTECTION_INHERIT


class Style(HashableObject):
    """Style object containing all formatting details."""
    __fields__ = ('font',
                  'fill',
                  'borders',
                  'alignment',
                  'number_format',
                  'protection')
    __slots__ = __fields__

    def __init__(self, static=False):
        super(Style, self).__init__()
        self.static = static
        self.font = Font()
        self.fill = Fill()
        self.borders = Borders()
        self.alignment = Alignment()
        self.number_format = NumberFormat()
        self.protection = Protection()

    def copy(self):
        new_style = Style()
        new_style.font = deepcopy(self.font)
        new_style.fill = deepcopy(self.fill)
        new_style.borders = deepcopy(self.borders)
        new_style.alignment = deepcopy(self.alignment)
        new_style.number_format = deepcopy(self.number_format)
        new_style.protection = deepcopy(self.protection)
        return new_style

DEFAULTS = Style()
