import rephorm
import datapie as dp 
import numpy as np

# *****************************************
# 📌 DISCLAIMER
#
# Abbreviations in Code:
#    - `cs` → ChartSeries (for chart data)
#    - `ts` → TableSeries (for table data)
#    - Similar shorthand names may be used throughout the code.
#
# Random Data Notice:
#    - This script generates random values for demonstration.
#    - Replace `ran()` with actual data sources
#    in real use cases.
#
# NumPy (`np`) Usage:
#    - Functions prefixed with `np` come from the NumPy library.
#    - NumPy is only used to generate test data.
#
# Any code where comments starting with "⚠️ Generate" or "⚠️" pertains
# to generating sample test data. It is purely for simulation purposes
# and does not require interpretation.
#
# *****************************************

# ─────────────────────────────────────────
# ⚠️ Random Data Generator ⚠️
# It creates random values and is NOT needed in real applications.
ran = lambda length: np.cumsum(np.random.normal(0, 10, length))


# ─────────────────────────────────────────

def main():
    # ────── REPORT initialization ──────

    report = rephorm.Report(
        title="Advanced Reporting",
        orientation="L"
    )

    # ─────────────────────────────────────────
    # Table (Page 2)
    # ─────────────────────────────────────────

    # `show_units=False` - Disable display of units column in the table. (By default it is shown)
    # decimal_precision - number of decimal places to display for numerical values.
    # highlight - Defines a span (range) of values to be visually emphasized.
    # When `dp.end` is used in the `highlight` value, it automatically selects
    # the last date of the `span` assigned to the table.
    # The default highlight color is grey.

    # Customizing the default highlight color:
    # - Use the `styles` parameter, which accepts a dictionary of key-value pairs (refer to documentation for more details).
    # - The `table` key contains another dictionary since multiple style attributes can be defined for the table.
    # - Set the `"highlight_color"` key to `"green"` to override the default highlight color for the table.

    table_span = dp.qq(2022, 1) >> dp.qq(2025, 1)

    table = rephorm.Table(
        title="Random Data Table",
        show_units=False,
        span=table_span,
        decimal_precision=1,
        highlight=dp.qq(2024, 3) >> dp.end,
        styles={
            "highlight_color": "green"
        }
    )

    # Creating Time Series Data and Adding It to the Table

    # Generate a time series dataset (ts_data_1)
    # dp.Series (dp - DATAPIE) creates a time series with:
    #   dates=table_span: Time period covered by the table.
    #   values=ran(len(table_span)): Randomly generated numerical values representing sample data.
    ts_data_1 = dp.Series(dates=table_span, values=ran(len(table_span)))

    # Adding the time series data to the table using rephorm.TableSeries.
    # rephorm.TableSeries creates a rephorm-specific TableSeries object that takes dp.Series data.
    # The `title="series a"` parameter sets the row label for this data series in the table.
    # The `unit="units"` parameter defines the measurement unit displayed for this data series in the table.
    table.add(rephorm.TableSeries(
        title="series a",
        data=ts_data_1,
    ))

    ts_data_2 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series b",
        data=ts_data_2,
    ))

    ts_data_3 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series c",
        data=ts_data_3,
        
    ))

    ts_data_4 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series d",
        data=ts_data_4,
    ))

    ts_data_5 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series e",
        data=ts_data_5,
    ))

    ts_data_6 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series f",
        data=ts_data_6
    ))

    ts_data_7 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series g",
        data=ts_data_7,
    ))

    ts_data_8 = dp.Series(dates=table_span, values=ran(len(table_span)))

    table.add(rephorm.TableSeries(
        title="series h",
        data=ts_data_8,
    ))

    # ────── Add table section ──────

    # TABLE SECTION: what is it?
    # A table section is used to categorize and structure related data within
    # a table, enhancing organization and visual separation of data categories.
    # It introduces a special row (consisting of a table section title followed
    # by empty cells) to create a clear distinction between grouped data sets.
    # This improves readability and simplifies the interpretation of segmented
    # data in the table.

    # TABLE SECTION: Logic flow
    # The TableSeries should be added to the TableSection,
    # and then the TableSection is added to the table.

    # TABLE SECTION: Styles
    # Customizing the highlight color for a table section:
    # - Setting `"highlight_color"` to `"yellow"` changes the section's highlight color, and it
    # applies the same style to TableSeries rows that are later added to this section. (This is how inheritance works)

    table_section = rephorm.TableSection("Table Section i",
                                        styles={
                                            "highlight_color": "yellow"
                                        })

    # Add table series to table section
    table_section.add(rephorm.TableSeries(
        title="series j",
        data=ts_data_1,
    ))

    # TABLE SERIES: Comparison series
    # A comparison series behaves just like a normal TableSeries but with different styling.
    # - Visually, it appears directly below the last added row (or TableSeries).
    # - It is created by adding `comparison_series=True` to the TableSeries object
    # - Additionally, you can set `compare_style` to "pct" to enclose values in parentheses "()",
    #   or to "diff" to enclose values in square brackets "[]".

    table_section.add(rephorm.TableSeries(
        title="comp_series",
        data=dp.pct(ts_data_1, -4),
        compare_style="pct",
        comparison_series=True,
    ))

    table_section.add(rephorm.TableSeries(
        title="series k",
        data=ts_data_2,
    ))

    table_section.add(rephorm.TableSeries(
        title="comp_series",
        data=dp.pct(ts_data_2, -4),
        compare_style="diff",
        comparison_series=True,
    ))

    # Add table section to the table:
    table.add(table_section)

    # Add table to the report
    report.add(table)

    # ─────────────────────────────────────────
    #  CHARTS
    # ─────────────────────────────────────────

    # ─────────────────────────────────────────
    # Black line chart (Page 3)
    # ─────────────────────────────────────────

    # Define the Date Range for the Chart
    chart_span = dp.qq(2020, 1) >> dp.qq(2028, 1)

    # ⚠️ Generate Random Data sample
    blackline_chart_data = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Create Black line chart
    # `axis_border=True`: when it is set to `True`, it adds borders to the top and right sides,
    #  creating a fully enclosed frame around the chart.
    chart = rephorm.Chart(
        span=chart_span,
        title="Black line chart",
        highlight=dp.qq(2026, 1) >> dp.qq(2028, 1),
        show_legend=False,
        axis_border=True,
    )

    # Adding Time Series Data to the Chart A
    # - `rephorm.ChartSeries` creates a ChartSeries object that takes (dp.Series) time series data (`blackline_chart_data`).
    # - `series_type="line"` specifies that the data will be displayed as a line chart.
    # - The `styles` dictionary allows customization of the 'series' appearance.
    # - `"chart.series.line_color": "black"` sets the color of the line to black.
    # - In the code example below, the `styles` parameter is written in a flat key-value structure.

    chart.add(rephorm.ChartSeries(
        blackline_chart_data,
        series_type="line",
        styles={
            "line_color": "black",
        }
    ))

    # - Alternatively, the same configuration can be structured as a nested dictionary:
    #
    #   styles = {
    #       "chart": {
    #           "series": {
    #               "line_color": "black"
    #           }
    #       }
    #   }
    #
    # - Both formats achieve the same result, providing flexibility in defining styles.

    # Add Black line chart to the Report
    report.add(chart)

    # ─────────────────────────────────────────
    # Legend position (Page 4)
    # ─────────────────────────────────────────

    # Define the Date Range for the Chart
    chart_span = dp.qq(2015, 1) >> dp.qq(2035, 1)

    # ────── Configuring Legend Position & Orientation ──────

    # legend_position="EO",
    # - Defines where the legend appears relative to the chart.
    # - `"EO"` stands for "East Outside," placing the legend **outside** the chart on the right side.
    # - Other possible values include:
    #   - `"N"` (North, top inside the chart)
    #   - `"S"` (South, bottom inside the chart)
    #   - `"E"` (East, right inside the chart)
    #   - `"W"` (West, left inside the chart)
    #   - `"NO"` (North Outside), `"SO"` (South Outside), etc.

    # legend_orientation="V",
    # - Determines how the legend items are arranged.
    # - `"V"` (Vertical) stacks legend entries one below the other.
    # - `"H"` (Horizontal) places legend entries side by side.

    # This feature is still experimental.
    # - If `"EO"` (East Outside) or `"WO"` (West Outside) is selected for `legend_position`,
    #   it is necessary to set `legend_orientation="V"` (Vertical).
    # - Failing to do so may disrupt the layout of the chart, causing misalignment.

    chart = rephorm.Chart(
        span=chart_span,
        title="Legend position: EO, legend orientation: V",
        legend_position="EO",
        legend_orientation="V",
        show_grid=True,
    )

    # ⚠️ Generate a random sample data for the curve.
    series_1 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Adding a time series to the chart:
    # - `rephorm.ChartSeries` creates a ChartSeries object using `series_1` time series data.
    # - `legend=("Custom",)` assigns a label for this series in the chart legend.
    # - `series_type="line"` specifies that the data should be plotted as a line chart.
    # - `markers_mode="lines"` ensures that only a continuous line is drawn, without markers.

    chart.add(rephorm.ChartSeries(
        series_1,
        legend=("Custom",),
        series_type="line",
        markers_mode="lines",
    ))

    # ⚠️ Generate a random sample data for the curve.
    series_2 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    chart.add(rephorm.ChartSeries(
        series_2,
        series_type="line",
        markers_mode="lines",
        styles={
            "line_color": "black",
        }
    ))

    # Add the Chart to the Report
    report.add(chart)

    # ─────────────────────────────────────────
    # Different line styles (Page 5)
    # ─────────────────────────────────────────

    # Define the Date Range for the Chart
    chart_span = dp.qq(2015, 1) >> dp.qq(2035, 1)

    chart = rephorm.Chart(
        span=chart_span,
        title="Different line styles",
        legend_position="EO",
        legend_orientation="V",
        show_grid=True,
    )

    # Add Time Series Data with Different Line Styles

    # ⚠️ Generate a random sample data for the curve.
    series_1 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Adding a time series to the chart:
    # - `rephorm.ChartSeries` creates a ChartSeries object using `series_1` time series data.
    # - `legend=("Custom",)` assigns a label for this series in the chart legend.
    # - `series_type="line"` specifies that the data should be plotted as a line chart.
    # - `markers_mode="lines"` ensures that only a continuous line is drawn, without markers.
    # - The `styles` dictionary customizes the appearance:
    #     - `"line_style": "dash"` makes the line dashed.
    #     - `"line_width": 2"` increase the thickness of the line. (default is 1)
    #     - `"line_color": "blue"` changes the line color to blue. (default is green)

    chart.add(rephorm.ChartSeries(
        series_1,
        legend=("dash",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_style": "dash",
            "line_width": 2,
            "line_color": "blue",
        }
    ))

    # ⚠️ Generate a random sample data for the curve.
    series_2 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    chart.add(rephorm.ChartSeries(
        series_2,
        legend=("dot",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_style": "dot",
            "line_width": 2,
            "line_color": "orange",
        }
    ))

    # ⚠️ Generate a random sample data for the curve.
    series_3 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    chart.add(rephorm.ChartSeries(
        series_3,
        legend=("dashdot",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_style": "dashdot",
            "line_width": 2,
            "line_color": "yellow",
        }
    ))

    # ⚠️ Generate a random sample data for the curve.
    series_4 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    chart.add(rephorm.ChartSeries(
        series_4,
        legend=("longdash",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_style": "longdash",
            "line_width": 2,
            "line_color": "purple",
        }
    ))

    # ⚠️ Generate a random sample data for the curve.
    series_5 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    chart.add(rephorm.ChartSeries(
        series_5,
        legend=("longdashdot",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_style": "longdashdot",
            "line_width": 2,
            "line_color": "magenta",
        }
    ))

    # Add the Chart to the Report
    report.add(chart)

    # ─────────────────────────────────────────
    # Single Marker Chart (Page 6)
    #
    # A chart where one of the time series (called "extension_series")
    # includes a marker, but only for the first data point.
    # ─────────────────────────────────────────

    # Specify Date Ranges
    main_span = dp.qq(2004, 1) >> dp.qq(2015, 2)
    extension_span = dp.qq(2010, 1) >> dp.qq(2015, 2)

    # Create Chart
    # - Instead of defining legends per series, `legend` can be set directly in `Chart`.
    chart = rephorm.Chart(
        title="Single marker Chart",
        span=main_span,
        axis_border=True,
    )

    # Creating Random Time Series Data Sample
    main_line_series = dp.Series(dates=main_span, values=ran(len(main_span)))

    # Adding Main Line Series
    chart.add(rephorm.ChartSeries(
        main_line_series,
        legend=("Main line",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_color": "blue",
        },
    ))

    # Creating Random Time Series Data Sample
    dashed_line_series = dp.Series(dates=extension_span, values=ran(len(extension_span)))

    # Adding Extension Line
    chart.add(rephorm.ChartSeries(
        dashed_line_series,
        legend=("Dashed line",),
        series_type="line",
        markers_mode="lines",
        styles={
            "line_color": "red",
            "line_style": "dash",
        },
    ))

    # Extract the first date & first value of extension series (for marker placement)
    first_date = dashed_line_series.dates[0]
    first_value = dashed_line_series.get_values()[0]

    # Create a single-point series for the marker
    first_point_series = dp.Series(dates=[first_date], values=[first_value])

    # Adds a single-point marker to the chart.
    # - show_legend=False: Hides the legend for this series.
    # - `series_type="line"`: Ensures compatibility within the chart.
    # - `markers_mode="markers"`: Displays only the markers.
    # - `chart.series.marker_size=10`: Sets the marker size.
    # - `chart.series.marker_width=2`: Sets the marker thickness.
    chart.add(rephorm.ChartSeries(
        first_point_series,
        show_legend=False,
        markers_mode="markers",
        styles={
            "marker_size": 10,
            "marker_width": 2,
            "marker_color": "red",
        },
    ))

    # Adding chart to the report
    report.add(chart)

    # ─────────────────────────────────────────
    # Contribution bar chart (Page 7)
    # ─────────────────────────────────────────

    # Define chart span
    plot_span = dp.qq(2014, 2) >> dp.qq(2022, 2)

    # Create chart object
    # The `<br>` tag within title string allows for a line break, enabling multi-line titles.
    # More than one <br> is not recommended. (Layout starts to break apart)
    shock_chart = rephorm.Chart(
        span=plot_span,
        title="Contribution bar <br> with multiline title",
        axis_border=True,
    )

    # Create and add bar series
    stacked_bar_series_1 = dp.Series(dates=plot_span, values=ran(len(plot_span)))
    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_series_1,
        series_type="conbar",
        styles={
            "bar_face_color": "forestgreen",
        }
    ))

    stacked_bar_series_2 = dp.Series(dates=plot_span, values=ran(len(plot_span)))
    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_series_2,
        series_type="conbar",
        styles={
            "bar_face_color": "darkgreen",
        }
    ))

    stacked_bar_series_3 = dp.Series(dates=plot_span, values=ran(len(plot_span)))
    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_series_3,
        series_type="conbar",
        styles={
            "bar_face_color": "cadetblue",
        }
    ))

    stacked_bar_series_4 = dp.Series(dates=plot_span, values=ran(len(plot_span)))
    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_series_4,
        series_type="conbar",
        styles={
            "bar_face_color": "olivedrab",
        }
    ))

    # ────── Compute the Element-Wise Sum ──────
    sum_series_values = np.add.reduce([
        stacked_bar_series_1.get_values(),
        stacked_bar_series_2.get_values(),
        stacked_bar_series_3.get_values(),
        stacked_bar_series_4.get_values()
    ])

    # Create Summed Line Series to Overlay on the Stacked Bars
    stacked_bar_line_series = dp.Series(dates=plot_span, values=sum_series_values)

    # ────── Adding a Border for the Line Series ──────
    # - rephorm does not support borders for line series natively. (Plotly neither)
    # - To simulate a border effect, we add two identical line series:
    #   1. The first line is plotted thicker in a neutral color to act as a "border."
    #   2. The second line, with a smaller width, is plotted on top to represent the actual data.
    #
    # The setup and data for both series are nearly identical.
    # The only differences are:
    #   1. `show_legend=False` for the first series, so it does not appear in the legend. (Important step)
    #   2. The `styles` parameters:
    #      - The first series has a thicker, neutral-colored line to create a "border" effect.
    #      - The second series represents the "actual" data, plotted on top with a distinct color and a thinner line.

    # Adding a thicker line in white color

    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_line_series,
        series_type="line",
        show_legend=False,
        markers_mode="lines",
        styles={
            "line_width": 5,
            "line_color": "white",
        }
    ))

    # Adding the actual (thinner) line
    shock_chart.add(rephorm.ChartSeries(
        stacked_bar_line_series,
        series_type="line",
        markers_mode="lines",
        styles={
            "line_width": 3,
            "line_color": "grey",
        }
    ))

    # add chart to the report
    report.add(shock_chart)

    # ─────────────────────────────────────────
    # Line & Markers Chart (Page 8)
    # ─────────────────────────────────────────

    # ⚠️ Generate Random Data
    some_random_data = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Create Line & Markers Chart
    # - highlight: same as in tables, if you specify highlight parameter
    # and pass range/span as a value, it will add highlight (grey by default)

    lines_and_markers_chart = rephorm.Chart(
        span=chart_span,
        title="Line & Markers Chart",
        highlight=dp.qq(2030, 1) >> dp.end,
        axis_border=True,
    )

    # Add Line Series
    # marker_symbol:
    #  - Specifies the marker shape
    # List of possible symbols is available in documentation
    lines_and_markers_chart.add(rephorm.ChartSeries(
        some_random_data,
        show_legend=True,
        legend=("RedLine",),
        series_type="line",
        markers_mode="lines",
        marker_symbol="square-open",
        styles={
            "line_color": "darkred",
        }
    ))

    # Add "suqare-open" (dark green) markers to the chart
    lines_and_markers_chart.add(rephorm.ChartSeries(
        some_random_data,
        show_legend=True,
        legend=("square-open",),
        series_type="line",
        markers_mode="markers",
        marker_symbol="square-open",
        styles={
            "marker_color": "darkgreen",
            "marker_size": 6,
        }
    ))

    # Add "x-thin" (green) markers to the chart
    lines_and_markers_chart.add(rephorm.ChartSeries(
        some_random_data,
        show_legend=True,
        legend=("x-thin",),
        series_type="line",
        markers_mode="markers",
        marker_symbol="x-thin",
        styles={
            "marker_color": "mediumseagreen",
            "marker_size": 6,
            "line_width": 1,
        }
    ))

    # Add chart to the report
    report.add(lines_and_markers_chart)

    # ─────────────────────────────────────────
    # Working with styles (Page 9)
    # ─────────────────────────────────────────

    # This section will show how can user use styles differently and use to his own advantage.

    # The `combination_chart_style` dictionary below works like a design template for our chart.
    # By storing all our visual settings (colors, sizes, etc.) in one place, we can easily
    # apply consistent styling to the entire chart and all its parts without having to
    # copy-paste the same style code over and over again.

    # Chart-level properties define the overall visual style, while nested "series" properties
    # cascade down to all data series (ChartSeries objects) contained within the chart.
    # This approach lets us override default styles while keeping our code concise/minimal.

    # By "centralizing" styling, you can quickly update specific chart's appearance
    # without searching through scattered code. This saves time and keeps
    # your design consistent, especially when you have multiple elements.

    combination_grid = rephorm.Grid()

    combination_chart_style = {
        # Chart-level properties control the overall chart appearance:
        "highlight_color": "rgba(200, 200, 200, 0.5)",
        "grid_color": "lightblue",
        # Series-level properties automatically cascade to all chart child (ChartSeries)
        # objects within the chart:
        "series": {
            "line_width": 2,
            "line_color": "green",
            "marker_color": "green",
            "marker_size": 6
        }
    }

    # Generate random data for demonstration
    random_data = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Create first chart and apply the previously defined style template as:
    # `styles=combination_chart_style`
    combination_chart = rephorm.Chart(
        span=chart_span,
        highlight=dp.qq(2028, 2) >> dp.qq(2031, 4),
        styles=combination_chart_style
    )

    # Add a Chart Series to the chart.
    # This series inherits its styles from the parent chart, which means it automatically
    # follows the settings defined in the `combination_chart_style` dictionary.
    combination_chart.add(rephorm.ChartSeries(
        random_data,
    ))

    # Add chart to grid
    combination_grid.add(combination_chart)

    # Create second chart
    # apply same `combination_chart_style` styles dictionary
    combination_chart_2 = rephorm.Chart(
        span=chart_span,
        highlight=dp.qq(2028, 2) >> dp.qq(2031, 4),
        styles=combination_chart_style
    )

    # Generate random data for demonstration
    random_data_2 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Add a Chart Series to the chart.
    combination_chart_2.add(rephorm.ChartSeries(
        random_data_2,
    ))

    # Add chart to grid
    combination_grid.add(combination_chart_2)

    # Create chart
    combination_chart_3 = rephorm.Chart(
        span=chart_span,
        highlight=dp.qq(2028, 2) >> dp.qq(2031, 4),
        styles=combination_chart_style
    )

    # Generate random data for demonstration
    random_data_3 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Add a Chart Series to the chart.
    combination_chart_3.add(rephorm.ChartSeries(
        random_data_3,
    ))

    # Add chart to grid
    combination_grid.add(combination_chart_3)

    # Requirements: The last chart should have the same styling as defined in the  
    # combination_chart_style dictionary, but the series' line_color must be "Black".
    combination_chart_4 = rephorm.Chart(
        span=chart_span,
        markers_mode="lines",
        highlight=dp.qq(2028, 2) >> dp.qq(2031, 4),
        styles=combination_chart_style
    )

    # Generate random data for demonstration
    random_data_4 = dp.Series(dates=chart_span, values=ran(len(chart_span)))

    # Add a Chart Series to the chart.
    combination_chart_4.add(rephorm.ChartSeries(
        random_data_4,
        styles={
            "line_color": "black"
        }
    ))

    # Add chart to grid
    combination_grid.add(combination_chart_4)

    report.add(combination_grid)

    # The idea of this page 9 section, was to show that when multiple charts needs 
    # the same custom styling (different from the default), a styles dictionary 
    # could be created once and reused for each chart (of that type). Without this, 
    # the styles would have to be repeated for every chart instance. This approach 
    # keeps the code cleaner and follows the DRY (Don't Repeat Yourself) principle.

    # ─────────────────────────────────────────
    # Generating Report as a PDF
    # ─────────────────────────────────────────
    # Compile and export the final report to a PDF file
    # by calling the `.output` method on the `report` object,
    # which contains all the added elements.
    report.output("advanced_report")


if __name__ == "__main__":
    main()