#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed 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.

"""Collection of helpers to visualize cp_model solutions in colab."""

# pylint: disable=g-import-not-at-top
import random

try:
    from IPython.display import display
    from IPython.display import SVG
    import plotly.figure_factory as ff
    import svgwrite

    correct_imports = True
except ImportError:
    correct_imports = False


def RunFromIPython():
    if not correct_imports:
        return False
    try:
        return __IPYTHON__ is not None
    except NameError:
        return False


def ToDate(v):
    return "2016-01-01 6:%02i:%02i" % (v / 60, v % 60)


class ColorManager:
    """Utility to create colors to use in visualization."""

    def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step):
        """Creates an interpolated rgb color between two rgb colors."""
        num_intervals = num_steps - 1
        dr = (er - sr) / num_intervals
        dg = (eg - sg) / num_intervals
        db = (eb - sb) / num_intervals
        r = sr + dr * step
        g = sg + dg * step
        b = sb + db * step
        return "rgb(%i, %i, %i)" % (r, g, b)

    def SeedRandomColor(self, seed=0):
        random.seed(seed)

    def RandomColor(self):
        return "rgb(%i,%i,%i)" % (
            random.randint(0, 255),
            random.randint(0, 255),
            random.randint(0, 255),
        )


def DisplayJobshop(starts, durations, machines, name):
    """Simple function to display a jobshop solution using plotly."""

    jobs_count = len(starts)
    machines_count = len(starts[0])
    all_machines = range(0, machines_count)
    all_jobs = range(0, jobs_count)
    df = []
    for i in all_jobs:
        for j in all_machines:
            df.append(
                dict(
                    Task="Resource%i" % machines[i][j],
                    Start=ToDate(starts[i][j]),
                    Finish=ToDate(starts[i][j] + durations[i][j]),
                    Resource="Job%i" % i,
                )
            )

    sorted_df = sorted(df, key=lambda k: k["Task"])

    colors = {}
    cm = ColorManager()
    cm.SeedRandomColor(0)
    for i in all_jobs:
        colors["Job%i" % i] = cm.RandomColor()

    fig = ff.create_gantt(
        sorted_df,
        colors=colors,
        index_col="Resource",
        title=name,
        show_colorbar=False,
        showgrid_x=True,
        showgrid_y=True,
        group_tasks=True,
    )
    fig.show()


class SvgWrapper:
    """Simple SVG wrapper to use in colab."""

    def __init__(self, sizex, sizey, scaling=20.0):
        self.__sizex = sizex
        self.__sizey = sizey
        self.__scaling = scaling
        self.__offset = scaling
        self.__dwg = svgwrite.Drawing(
            size=(
                self.__sizex * self.__scaling + self.__offset,
                self.__sizey * self.__scaling + self.__offset * 2,
            )
        )

    def Display(self):
        display(SVG(self.__dwg.tostring()))

    def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None):
        """Draw a rectangle, dx and dy must be >= 0."""
        s = self.__scaling
        o = self.__offset
        corner = (x * s + o, (self.__sizey - y - dy) * s + o)
        size = (dx * s - 1, dy * s - 1)
        self.__dwg.add(
            self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke)
        )
        self.AddText(x + 0.5 * dx, y + 0.5 * dy, label)

    def AddText(self, x, y, label):
        text = self.__dwg.text(
            label,
            insert=(
                x * self.__scaling + self.__offset,
                (self.__sizey - y) * self.__scaling + self.__offset,
            ),
            text_anchor="middle",
            font_family="sans-serif",
            font_size="%dpx" % (self.__scaling / 2),
        )
        self.__dwg.add(text)

    def AddXScale(self, step=1):
        """Add an scale on the x axis."""
        o = self.__offset
        s = self.__scaling
        y = self.__sizey * s + o / 2.0 + o
        dy = self.__offset / 4.0
        self.__dwg.add(
            self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black")
        )
        for i in range(0, int(self.__sizex) + 1, step):
            self.__dwg.add(
                self.__dwg.line(
                    (o + i * s, y - dy), (o + i * s, y + dy), stroke="black"
                )
            )

    def AddYScale(self, step=1):
        """Add an scale on the y axis."""
        o = self.__offset
        s = self.__scaling
        x = o / 2.0
        dx = self.__offset / 4.0
        self.__dwg.add(
            self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black")
        )
        for i in range(0, int(self.__sizey) + 1, step):
            self.__dwg.add(
                self.__dwg.line(
                    (x - dx, i * s + o), (x + dx, i * s + o), stroke="black"
                )
            )

    def AddTitle(self, title):
        """Add a title to the drawing."""
        text = self.__dwg.text(
            title,
            insert=(
                self.__offset + self.__sizex * self.__scaling / 2.0,
                self.__offset / 2,
            ),
            text_anchor="middle",
            font_family="sans-serif",
            font_size="%dpx" % (self.__scaling / 2),
        )
        self.__dwg.add(text)
