# -*- coding: utf-8 -*-
#----------------------------------------------------------------------------
# Name:         divided.py
# Purpose:      DividedShape class
#
# Author:       Pierre Hjälm (from C++ original by Julian Smart)
#
# Created:      2004-05-08
# Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
# Licence:      wxWindows license
# Tags:         phoenix-port, unittest, py3-port, documented
#----------------------------------------------------------------------------
"""
The :class:`~lib.ogl.divided.DividedShape` class.
"""
import sys
import wx

from .basic import ControlPoint, RectangleShape, Shape
from .oglmisc import *


class DividedShapeControlPoint(ControlPoint):
    """The class:`DividedShapeControlPoint` class."""
    def __init__(self, the_canvas, object, region, size, the_m_xoffset, the_m_yoffset, the_type):
        """
        Default class constructor.

        :param `theCanvas`: a :class:`~lib.ogl.Canvas`
        :param `object`: not used ???
        :param `region`: an instance of :class:`~lib.ogl.ShapeRegion`
        :param float `size`: the size
        :param float `the_m_xoffset`: the the_m_xoffset position ???
        :param float `the_m_yoffset`: the the_m_yoffset position ???
        :param int `the_type`: one of the following types

         ======================================== ==================================
         Control point type                       Description
         ======================================== ==================================
         `CONTROL_POINT_VERTICAL`                 Vertical
         `CONTROL_POINT_HORIZONTAL`               Horizontal
         `CONTROL_POINT_DIAGONAL`                 Diagonal
         ======================================== ==================================

        """
        ControlPoint.__init__(self, the_canvas, object, size, the_m_xoffset, the_m_yoffset, the_type)
        self.regionId = region

    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
        """The drag left handler."""
        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas().GetBuffer())
        self.GetCanvas().PrepareDC(dc)
        dc.SetLogicalFunction(OGLRBLF)

        dottedPen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_DOT)
        dc.SetPen(dottedPen)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)

        dividedObject = self._shape
        x1 = dividedObject.GetX() - dividedObject.GetWidth() / 2.0
        y1 = y
        x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2.0
        y2 = y

        dc.DrawLine(x1, y1, x2, y2)

    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
        """The begin drag left handler."""
        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas().GetBuffer())
        self.GetCanvas().PrepareDC(dc)
        dc.SetLogicalFunction(OGLRBLF)

        dottedPen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_DOT)
        dc.SetPen(dottedPen)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)

        dividedObject = self._shape

        x1 = dividedObject.GetX() - dividedObject.GetWidth() / 2.0
        y1 = y
        x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2.0
        y2 = y

        dc.DrawLine(x1, y1, x2, y2)
        self._canvas.CaptureMouse()

    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
        """The end drag left handler."""
        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas().GetBuffer())
        self.GetCanvas().PrepareDC(dc)

        dividedObject = self._shape
        if not dividedObject.GetRegions()[self.regionId]:
            return

        thisRegion = dividedObject.GetRegions()[self.regionId]
        nextRegion = None

        dc.SetLogicalFunction(wx.COPY)

        if self._canvas.HasCapture():
            self._canvas.ReleaseMouse()

        # Find the old top and bottom of this region,
        # and calculate the new proportion for this region
        # if legal.
        currentY = dividedObject.GetY() - dividedObject.GetHeight() / 2.0
        maxY = dividedObject.GetY() + dividedObject.GetHeight() / 2.0

        # Save values
        theRegionTop = 0
        nextRegionBottom = 0

        for i in range(len(dividedObject.GetRegions())):
            region = dividedObject.GetRegions()[i]
            proportion = region._regionProportionY
            yy = currentY + dividedObject.GetHeight() * proportion
            actualY = min(maxY, yy)

            if region == thisRegion:
                thisRegionTop = currentY

                if i + 1 < len(dividedObject.GetRegions()):
                    nextRegion = dividedObject.GetRegions()[i + 1]
            if region == nextRegion:
                nextRegionBottom = actualY

            currentY = actualY

        if not nextRegion:
            return

        # Check that we haven't gone above this region or below
        # next region.
        if y <= thisRegionTop or y >= nextRegionBottom:
            return

        dividedObject.EraseLinks(dc)

        # Now calculate the new proportions of this region and the next region
        thisProportion = float(y - thisRegionTop) / dividedObject.GetHeight()
        nextProportion = float(nextRegionBottom - y) / dividedObject.GetHeight()

        thisRegion.SetProportions(0, thisProportion)
        nextRegion.SetProportions(0, nextProportion)
        self._yoffset = y - dividedObject.GetY()

        # Now reformat text
        for i, region in enumerate(dividedObject.GetRegions()):
            if region.GetText():
                s = region.GetText()
                dividedObject.FormatText(dc, s, i)

        dividedObject.SetRegionSizes()
        dividedObject.Draw(dc)
        dividedObject.GetEventHandler().OnMoveLinks(dc)


class DividedShape(RectangleShape):
    """
    A :class:`DividedShape` is a rectangle with a number of vertical divisions.
    Each division may have its text formatted with independent characteristics,
    and the size of each division relative to the whole image may be specified.
    """
    def __init__(self, w, h):
        """
        Default class constructor.

        :param `w`: width of rectangle
        :param `h`: height of rectangle

        """
        RectangleShape.__init__(self, w, h)
        self.ClearRegions()

    def OnDraw(self, dc):
        """The draw handler."""
        RectangleShape.OnDraw(self, dc)

    def OnDrawContents(self, dc):
        """The draw contents handler."""
        if self.GetRegions():
            defaultProportion = 1.0 / len(self.GetRegions())
        else:
            defaultProportion = 0.0
        currentY = self._ypos - self._height / 2.0
        maxY = self._ypos + self._height / 2.0

        leftX = self._xpos - self._width / 2.0
        rightX = self._xpos + self._width / 2.0

        if self._pen:
            dc.SetPen(self._pen)

        dc.SetTextForeground(self._textColour)

        # For efficiency, don't do this under X - doesn't make
        # any visible difference for our purposes.
        if sys.platform[:3] == "win":
            dc.SetTextBackground(self._brush.GetColour())

        if self.GetDisableLabel():
            return

        xMargin = 2
        yMargin = 2

        dc.SetBackgroundMode(wx.TRANSPARENT)

        for region in self.GetRegions():
            dc.SetFont(region.GetFont())
            dc.SetTextForeground(region.GetActualColourObject())

            if region._regionProportionY < 0:
                proportion = defaultProportion
            else:
                proportion = region._regionProportionY

            y = currentY + self._height * proportion
            actualY = min(maxY, y)

            centreX = self._xpos
            centreY = currentY + (actualY - currentY) / 2.0

            DrawFormattedText(dc, region._formattedText, centreX, centreY, self._width - 2 * xMargin, actualY - currentY - 2 * yMargin, region._formatMode)

            if y <= maxY and region != self.GetRegions()[-1]:
                regionPen = region.GetActualPen()
                if regionPen:
                    dc.SetPen(regionPen)
                    dc.DrawLine(int(leftX), int(y), int(rightX), int(y))

            currentY = actualY

    def SetSize(self, w, h, recursive = True):
        """
        Set the size.

        :param `w`: width of rectangle
        :param `h`: height of rectangle
        :param `recursive`: not implemented

        """
        self.SetAttachmentSize(w, h)
        self._width = w
        self._height = h
        self.SetRegionSizes()

    def SetRegionSizes(self):
        """
        Set all region sizes according to proportions and this object
        total size.
        """
        if not self.GetRegions():
            return

        if self.GetRegions():
            defaultProportion = 1.0 / len(self.GetRegions())
        else:
            defaultProportion = 0.0
        currentY = self._ypos - self._height / 2.0
        maxY = self._ypos + self._height / 2.0

        for region in self.GetRegions():
            if region._regionProportionY <= 0:
                proportion = defaultProportion
            else:
                proportion = region._regionProportionY

            sizeY = proportion * self._height
            y = currentY + sizeY
            actualY = min(maxY, y)

            centreY = currentY + (actualY - currentY) / 2.0

            region.SetSize(self._width, sizeY)
            region.SetPosition(0, centreY - self._ypos)

            currentY = actualY

    def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
        """
        Get the attachment position.

        Attachment points correspond to regions in the divided box.

        :param `attachment`: the attachment ???
        :param `nth`: get nth attachment ???
        :param `no_arcs`: ???
        :param `line`: ???

        """
        totalNumberAttachments = len(self.GetRegions()) * 2 + 2
        if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE or attachment >= totalNumberAttachments:
            return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs)

        n = len(self.GetRegions())
        isEnd = line and line.IsEnd(self)

        left = self._xpos - self._width / 2.0
        right = self._xpos + self._width / 2.0
        top = self._ypos - self._height / 2.0
        bottom = self._ypos + self._height / 2.0

        # Zero is top, n + 1 is bottom
        if attachment == 0:
            y = top
            if self._spaceAttachments:
                if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
                    # Align line according to the next handle along
                    point = line.GetNextControlPoint(self)
                    if point[0] < left:
                        x = left
                    elif point[0] > right:
                        x = right
                    else:
                        x = point[0]
                else:
                    x = left + (nth + 1) * self._width / (no_arcs + 1.0)
            else:
                x = self._xpos
        elif attachment == n + 1:
            y = bottom
            if self._spaceAttachments:
                if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
                    # Align line according to the next handle along
                    point = line.GetNextControlPoint(self)
                    if point[0] < left:
                        x = left
                    elif point[0] > right:
                        x = right
                    else:
                        x = point[0]
                else:
                    x = left + (nth + 1) * self._width / (no_arcs + 1.0)
            else:
                x = self._xpos
        else: # Left or right
            isLeft = not attachment < (n + 1)
            if isLeft:
                i = totalNumberAttachments - attachment - 1
            else:
                i = attachment - 1

            region = self.GetRegions()[i]
            if region:
                if isLeft:
                    x = left
                else:
                    x = right

                # Calculate top and bottom of region
                top = self._ypos + region._y - region._height / 2.0
                bottom = self._ypos + region._y + region._height / 2.0

                # Assuming we can trust the absolute size and
                # position of these regions
                if self._spaceAttachments:
                    if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
                        # Align line according to the next handle along
                        point = line.GetNextControlPoint(self)
                        if point[1] < bottom:
                            y = bottom
                        elif point[1] > top:
                            y = top
                        else:
                            y = point[1]
                    else:
                        y = top + (nth + 1) * region._height / (no_arcs + 1.0)
                else:
                    y = self._ypos + region._y
            else:
                return False
        return x, y

    def GetNumberOfAttachments(self):
        """
        Get the number of attachments.

        There are two attachments for each region (left and right),
        plus one on the top and one on the bottom.
        """
        n = len(self.GetRegions()) * 2 + 2

        maxN = n - 1
        for point in self._attachmentPoints:
            if point._id > maxN:
                maxN = point._id

        return maxN + 1

    def AttachmentIsValid(self, attachment):
        """
        Is the attachment valid?

        :param `attachment`: the attachment

        """
        totalNumberAttachments = len(self.GetRegions()) * 2 + 2
        if attachment >= totalNumberAttachments:
            return Shape.AttachmentIsValid(self, attachment)
        else:
            return attachment >= 0

    def MakeControlPoints(self):
        """
        Make the control points.
        """
        RectangleShape.MakeControlPoints(self)
        self.MakeMandatoryControlPoints()

    def MakeMandatoryControlPoints(self):
        """
        Make the mandatory control points.
        """
        currentY = self.GetY() - self._height / 2.0
        maxY = self.GetY() + self._height / 2.0

        for i, region in enumerate(self.GetRegions()):
            proportion = region._regionProportionY

            y = currentY + self._height * proportion
            actualY = min(maxY, y)

            if region != self.GetRegions()[-1]:
                controlPoint = DividedShapeControlPoint(self._canvas, self, i, CONTROL_POINT_SIZE, 0, actualY - self.GetY(), 0)
                self._canvas.AddShape(controlPoint)
                self._controlPoints.append(controlPoint)

            currentY = actualY

    def ResetControlPoints(self):
        """
        Reset the control points.

        :note: May only have the region handles, (n - 1) of them

        """

        if len(self._controlPoints) > len(self.GetRegions()) - 1:
            RectangleShape.ResetControlPoints(self)

        self.ResetMandatoryControlPoints()

    def ResetMandatoryControlPoints(self):
        """
        Reset the mandatory control points.
        """
        currentY = self.GetY() - self._height / 2.0
        maxY = self.GetY() + self._height / 2.0

        i = 0
        for controlPoint in self._controlPoints:
            if isinstance(controlPoint, DividedShapeControlPoint):
                region = self.GetRegions()[i]
                proportion = region._regionProportionY

                y = currentY + self._height * proportion
                actualY = min(maxY, y)

                controlPoint._xoffset = 0
                controlPoint._yoffset = actualY - self.GetY()

                currentY = actualY

                i += 1

    def EditRegions(self):
        """
        Edit the region colours and styles. Not implemented.
        """
        print("EditRegions() is unimplemented")

    def OnRightClick(self, x, y, keys = 0, attachment = 0):
        """The right click handler."""
        if keys & KEY_CTRL:
            self.EditRegions()
        else:
            RectangleShape.OnRightClick(self, x, y, keys, attachment)
