#!/usr/bin/env python3
# (C) 2023 Cadence Design Systems, Inc. (Cadence)
# All rights reserved.
# TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
# provided to current licensees or subscribers of Cadence products or
# SaaS offerings (each a "Customer").
# Customer is hereby permitted to use, copy, and modify the Sample Code,
# subject to these terms. Cadence claims no rights to Customer's
# modifications. Modification of Sample Code is at Customer's sole and
# exclusive risk. Sample Code may require Customer to have a then
# current license or subscription to the applicable Cadence offering.
# THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED.  OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall Cadence be
# liable for any damages or liability in connection with the Sample Code
# or its use.


"""Generates an OpenEye Christmas card."""


import math
import os
import sys
from random import Random

from openeye import oechem, oedepict


def main() -> int:  # noqa: PLR0915
    """Depict Christmas card."""
    image_width, image_height = 400, 500
    image = oedepict.OEImage(image_width, image_height)

    # draw fir tree trunk
    trunk = []
    treetop = oedepict.OE2DPoint(image_width / 2.0, 100.0)
    trunk.append(oedepict.OE2DPoint(treetop.GetX(), treetop.GetY() + 40.0))
    trunk.append(oedepict.OE2DPoint(treetop.GetX() - 10.0, treetop.GetY() + 330.0))
    trunk.append(oedepict.OE2DPoint(treetop.GetX() + 10.0, treetop.GetY() + 330.0))

    trunk_pen = oedepict.OEPen(
        oechem.OEBrown,
        oechem.OEBrown,
        oedepict.OEFill_On,
        1.0,
        oedepict.OEStipple_NoLine,
    )
    image.DrawPolygon(trunk, trunk_pen)

    # draw fir tree branches
    branches = []
    branches.append(treetop)
    for i in range(1, 8):
        p = oedepict.OE2DPoint(treetop.GetX() + i * 20.0, treetop.GetY() + i * 40.0)
        branches.append(p)
        branches.append(oedepict.OE2DPoint(p.GetX() - i * 7.0, p.GetY()))
        p = oedepict.OE2DPoint(treetop.GetX() - i * 20.0, treetop.GetY() + i * 40.0)
        branches.insert(0, p)
        branches.insert(0, oedepict.OE2DPoint(p.GetX() + i * 7.0, p.GetY()))

    branch_color = oechem.OEColor(oechem.OEDarkGreen)
    branch_color.SetA(200)
    branch_pen = oedepict.OEPen(
        branch_color, branch_color, oedepict.OEFill_On, 1.0, oedepict.OEStipple_NoLine
    )
    image.DrawPolygon(branches, branch_pen)

    # draw star
    octo = []
    for i in range(5):
        x = treetop.GetX() + 20.0 * math.cos(i * 2.0 * math.pi / 5.0)
        y = treetop.GetY() + 20.0 * math.sin(i * 2.0 * math.pi / 5.0)
        octo.append(oedepict.OE2DPoint(x, y))
    star = [octo[0], octo[2], octo[4], octo[1], octo[3]]

    star_pen = oedepict.OEPen(
        oechem.OEYellow,
        oechem.OEYellow,
        oedepict.OEFill_On,
        1.0,
        oedepict.OEStipple_NoLine,
    )
    image.DrawPolygon(star, star_pen)

    font = oedepict.OEFont(
        oedepict.OEFontFamily_Default,
        oedepict.OEFontStyle_Default,
        30,
        oedepict.OEAlignment_Center,
        oechem.OEDarkRed,
    )
    center = treetop + oedepict.OE2DPoint(0.0, 100.0)
    radius = 150.0

    text = "Happy Christmas"
    axis = oedepict.OE2DPoint(0.0, -radius)
    angle_inc = 120.0 / len(text)
    angle = 300.0 + angle_inc / 2.0
    for letter in text:
        pos = center + _rotate_2d_point(axis, angle)
        font.SetRotationAngle((360 - angle) % 360)
        image.DrawText(pos, letter, font)
        angle += angle_inc

    # draw presents
    box_color_gradient = oechem.OELinearColorGradient()
    box_color_gradient.AddStop(oechem.OEColorStop(0.0, oechem.OELightBlue))
    box_color_gradient.AddStop(oechem.OEColorStop(1.0, oechem.OEDarkPurple))

    rand = Random(0)  # noqa: S311

    for i in range(1, 5):
        box_color = box_color_gradient.GetColorAt(rand.random())
        pos = oedepict.OE2DPoint(treetop.GetX() - i * 30.0, treetop.GetY() + 330.0)
        _draw_present(image, rand, pos, box_color)

        box_color = box_color_gradient.GetColorAt(rand.random())
        pos = oedepict.OE2DPoint(treetop.GetX() + i * 30.0, treetop.GetY() + 330.0)
        _draw_present(image, rand, pos, box_color)

    # draw ornaments
    ball_color_gradient = oechem.OELinearColorGradient()
    ball_color_gradient.AddStop(oechem.OEColorStop(0.0, oechem.OEYellow))
    ball_color_gradient.AddStop(oechem.OEColorStop(1.0, oechem.OEDarkRed))

    for i in range(2, 9):
        ball_color = ball_color_gradient.GetColorAt(rand.random())
        ball_pos = oedepict.OE2DPoint(treetop.GetX(), treetop.GetY() + i * 30)
        _draw_ball(image, rand, ball_pos, ball_color)

    for i in range(2, 8):
        for x_sign in [10.0, -10.0]:
            ball_color = ball_color_gradient.GetColorAt(rand.random())
            ball_pos = oedepict.OE2DPoint(
                treetop.GetX() + i * x_sign, treetop.GetY() + i * 35
            )
            _draw_ball(image, rand, ball_pos, ball_color)

    for i in range(2, 9):
        for x_sign in [5.0, -5.0]:
            ball_color = ball_color_gradient.GetColorAt(rand.random())
            ball_pos = oedepict.OE2DPoint(
                treetop.GetX() + i * x_sign, treetop.GetY() + 50 + i * 25
            )
            _draw_ball(image, rand, ball_pos, ball_color)

    font = oedepict.OEFont(
        oedepict.OEFontFamily_Default,
        oedepict.OEFontStyle_Default,
        10,
        oedepict.OEAlignment_Right,
        oechem.OELightGrey,
    )
    text = "Generated by OEDepict TK"
    image.DrawText(
        oedepict.OE2DPoint(image_width - 20.0, image_height - 20.0), text, font
    )

    oedepict.OEWriteImage("oexmas.svg", image)

    return os.EX_OK


def _draw_present(
    image: oedepict.OEImageBase,
    rand: Random,
    box_pos: oedepict.OE2DPoint,
    box_color: oechem.OEColor,
) -> None:

    box_pen = oedepict.OEPen(
        box_color, box_color, oedepict.OEFill_On, 1.0, oedepict.OEStipple_NoLine
    )
    box_size = rand.randint(15, 28)

    tl = oedepict.OE2DPoint(box_pos.GetX() - box_size / 2.0, box_pos.GetY() - box_size)
    br = oedepict.OE2DPoint(box_pos.GetX() + box_size / 2.0, box_pos.GetY())
    image.DrawRectangle(tl, br, box_pen)

    ribbon_pen = oedepict.OEPen(
        oechem.OELightGrey, oechem.OELightGrey, oedepict.OEFill_On, 3.0
    )
    ribbon_pen.SetLineCap(oedepict.OELineCap_Butt)
    bgn = box_pos
    end = box_pos + oedepict.OE2DPoint(0.0, -box_size)
    image.DrawLine(bgn, end, ribbon_pen)

    bgn = oedepict.OE2DPoint(
        box_pos.GetX() - box_size / 2.0, box_pos.GetY() - box_size / 2.0
    )
    end = oedepict.OE2DPoint(
        box_pos.GetX() + box_size / 2.0, box_pos.GetY() - box_size / 2.0
    )
    image.DrawLine(bgn, end, ribbon_pen)


def _draw_ball(
    image: oedepict.OEImageBase,
    rand: Random,
    ball_pos: oedepict.OE2DPoint,
    ball_color: oechem.OEColor,
) -> None:
    # randomize position
    ball_pos.SetX(ball_pos.GetX() + rand.uniform(0, ball_pos.GetX() / 25.0))
    ball_pos.SetY(ball_pos.GetY() + rand.uniform(0, ball_pos.GetY() / 25.0))

    ball_pen = oedepict.OEPen(ball_color, ball_color, oedepict.OEFill_On, 1.0)
    ball_radius = rand.randint(3, 6)
    image.DrawCircle(ball_pos, ball_radius, ball_pen)


def _rotate_2d_point(p: oedepict.OE2DPoint, degree: float) -> oedepict.OE2DPoint:
    rad = degree * oechem.Deg2Rad
    cos_rad = math.cos(rad)
    sin_rad = math.sin(rad)
    return oedepict.OE2DPoint(
        cos_rad * p.GetX() - sin_rad * p.GetY(), sin_rad * p.GetX() + cos_rad * p.GetY()
    )


if __name__ == "__main__":
    sys.exit(main())
