# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you 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
#
#   http://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.


import sys
from typing import TYPE_CHECKING, List, Optional

# necessary to support types for Python 3.7
if TYPE_CHECKING:
    if sys.version_info >= (3, 8):
        from typing import Literal, TypedDict
    else:
        from typing_extensions import Literal, TypedDict

    Orientation = Literal['portrait', 'landscape']

    class _MarginOpts(TypedDict, total=False):
        left: float
        right: float
        top: float
        bottom: float

    class _PageOpts(TypedDict, total=False):
        width: float
        height: float

    class _PrintOpts(TypedDict, total=False):
        margin: _MarginOpts
        page: _PageOpts
        background: bool
        orientation: Orientation
        scale: float
        shrinkToFit: bool
        pageRanges: List[str]
else:
    from typing import Any, Dict

    Orientation = str
    _MarginOpts = _PageOpts = _PrintOpts = Dict[str, Any]


class PrintOptions:
    ORIENTATION_VALUES = ['portrait', 'landscape']

    def __init__(self) -> None:
        self._print_options: _PrintOpts = {}
        self._page: _PageOpts = {}
        self._margin: _MarginOpts = {}

    def to_dict(self) -> _PrintOpts:
        """
        :Returns: A hash of print options configured
        """
        return self._print_options

    @property
    def orientation(self) -> Optional[Orientation]:
        """
        :Returns: Orientation that was set for the page
        """
        return self._print_options.get('orientation', None)

    @orientation.setter
    def orientation(self, value: Orientation) -> None:
        """
        Allows you to set orientation of the page
        :Args:
         - value: Either portrait or landscape
        """
        if value not in self.ORIENTATION_VALUES:
            raise ValueError(f'Orientation value must be one of {self.ORIENTATION_VALUES}')

        self._print_options['orientation'] = value

    @property
    def scale(self) -> Optional[float]:
        """
        :Returns: Scale that was set for the page
        """
        return self._print_options.get('scale', None)

    @scale.setter
    def scale(self, value: float) -> None:
        """
        Allows you to to set scale for the page
        :Args:
         - value: integer or float between 0.1 and 2
        """
        self.__validate_num_property('Scale', value)

        if value < 0.1 or value > 2:
            raise ValueError('Scale value should be between 0.1 and 2')

        self._print_options['scale'] = value

    @property
    def background(self) -> Optional[bool]:
        """
        :Returns: Background value that was set
        """
        return self._print_options.get('background', None)

    @background.setter
    def background(self, value: bool) -> None:
        """
        Allows you to set the boolean value for the background
        :Args:
         - value: Boolean
        """
        if not isinstance(value, bool):
            raise ValueError('Set background value should be a boolean')
        self._print_options['background'] = value

    @property
    def page_width(self) -> Optional[float]:
        """
        :Returns: Page width that was set
        """
        return self._page.get('width', None)

    @page_width.setter
    def page_width(self, value: float) -> None:
        """
        Allows you to set width of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Page Width', value)

        self._page['width'] = value
        self._print_options['page'] = self._page

    @property
    def page_height(self) -> Optional[float]:
        """
        :Returns: Page height that was set
        """
        return self._page.get('height', None)

    @page_height.setter
    def page_height(self, value: float) -> None:
        """
        Allows you to set height of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Page Height', value)

        self._page['height'] = value
        self._print_options['page'] = self._page

    @property
    def margin_top(self) -> Optional[float]:
        """
        :Returns: Top margin of the page
        """
        return self._margin.get('top', None)

    @margin_top.setter
    def margin_top(self, value: float) -> None:
        """
        Allows you to set top margin of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Margin top', value)

        self._margin['top'] = value
        self._print_options['margin'] = self._margin

    @property
    def margin_left(self) -> Optional[float]:
        """
        :Returns: Left margin of the page
        """
        return self._margin.get('left', None)

    @margin_left.setter
    def margin_left(self, value: float) -> None:
        """
        Allows you to set left margin of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Margin left', value)

        self._margin['left'] = value
        self._print_options['margin'] = self._margin

    @property
    def margin_bottom(self) -> Optional[float]:
        """
        :Returns: Bottom margin of the page
        """
        return self._margin.get('bottom', None)

    @margin_bottom.setter
    def margin_bottom(self, value: float) -> None:
        """
        Allows you to set bottom margin of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Margin bottom', value)

        self._margin['bottom'] = value
        self._print_options['margin'] = self._margin

    @property
    def margin_right(self) -> Optional[float]:
        """
        :Returns: Right margin of the page
        """
        return self._margin.get('right', None)

    @margin_right.setter
    def margin_right(self, value: float) -> None:
        """
        Allows you to set right margin of the page
        :Args:
         - value: A positive integer or float
        """
        self.__validate_num_property('Margin right', value)

        self._margin['right'] = value
        self._print_options['margin'] = self._margin

    @property
    def shrink_to_fit(self) -> Optional[bool]:
        """
        :Returns: Value set for shrinkToFit
        """
        return self._print_options.get('shrinkToFit', None)

    @shrink_to_fit.setter
    def shrink_to_fit(self, value: bool) -> None:
        """
        Allows you to set shrinkToFit
        :Args:
         - value: Boolean
        """
        if not isinstance(value, bool):
            raise ValueError('Set shrink to fit value should be a boolean')
        self._print_options['shrinkToFit'] = value

    @property
    def page_ranges(self) -> Optional[List[str]]:
        """
        :Returns: value set for pageRanges
        """
        return self._print_options.get('pageRanges', None)

    @page_ranges.setter
    def page_ranges(self, value: List[str]) -> None:
        """
        Allows you to set pageRanges for the print command
        :Args:
         - value: A list of page ranges. Eg: ['1-2']
        """
        if not isinstance(value, list):
            raise ValueError('Page ranges should be a list')
        self._print_options['pageRanges'] = value

    def __validate_num_property(self, property_name: str, value: float) -> None:
        """
        Helper function to validate some of the properties
        """
        if not isinstance(value, (int, float)):
            raise ValueError(f'{property_name} should be an integer or a float')

        if value < 0:
            raise ValueError(f'{property_name} cannot be less then 0')
