"""Forecast Controller Module"""
__docformat__ = "numpy"

# pylint: disable=too-many-lines, too-many-branches, too-many-arguments, R0904,R0902,W0707
# flake8: noqa
import argparse
import logging
from typing import Any, Optional, List, Dict

try:
    import torch

    import darts

    darts_latest = "0.22.0"
    # check darts version
    if darts.__version__ != darts_latest:
        print(f"You are currently using Darts version {darts.__version__}")
        print(
            "Follow instructions on creating a new conda environment with the latest "
            f"Darts version ({darts_latest}):"
        )
        print("https://docs.openbb.co/terminal/quickstart/installation")
except ModuleNotFoundError:
    raise ModuleNotFoundError(
        "Please install the forecast version of the terminal. Instructions can be found "
        "under the python tab: https://docs.openbb.co/terminal/quickstart/installation"
    )
import pandas as pd
import psutil
from openbb_terminal.core.config.paths import (
    USER_EXPORTS_DIRECTORY,
    USER_CUSTOM_IMPORTS_DIRECTORY,
)
from openbb_terminal import feature_flags as obbff
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import (
    check_positive,
    check_positive_float,
    NO_EXPORT,
    EXPORT_ONLY_FIGURES_ALLOWED,
    EXPORT_ONLY_RAW_DATA_ALLOWED,
    log_and_raise,
    valid_date,
)

from openbb_terminal.menu import session
from openbb_terminal.parent_classes import BaseController
from openbb_terminal.rich_config import console, MenuText
from openbb_terminal.forecast import (
    forecast_model,
    forecast_view,
    autoselect_view,
    autoarima_view,
    autoces_view,
    autoets_view,
    mstl_view,
    rwd_view,
    seasonalnaive_view,
    expo_model,
    expo_view,
    linregr_view,
    nbeats_view,
    regr_view,
    tcn_view,
    theta_view,
    rnn_view,
    brnn_view,
    tft_view,
    helpers,
    trans_view,
    nhits_view,
)

from openbb_terminal.common import common_model

logger = logging.getLogger(__name__)
empty_df = pd.DataFrame()


def check_greater_than_one(value) -> int:
    """Argparse type to check positive int above 1"""
    new_value = int(value)
    if new_value <= 1:
        log_and_raise(
            argparse.ArgumentTypeError(
                f"{value} is an invalid positive int value. Must be greater than 1."
            )
        )
    return new_value


class ForecastController(BaseController):
    """Forecast class"""

    CHOICES_COMMANDS: List[str] = [
        "load",
        "show",
        "plot",
        "clean",
        "combine",
        "desc",
        "corr",
        "rename",
        "delete",
        "export",
        "ema",
        "sto",
        "rsi",
        "roc",
        "mom",
        "delta",
        "atr",
        "signal",
        "autoselect",
        "autoarima",
        "autoces",
        "autoets",
        "mstl",
        "rwd",
        "seasonalnaive",
        "expo",
        "theta",
        "rnn",
        "brnn",
        "nbeats",
        "tcn",
        "regr",
        "linregr",
        "trans",
        "tft",
        "season",
        "which",
        "nhits",
    ]
    pandas_plot_choices = [
        "line",
        "scatter",
        "bar",
        "barh",
        "hist",
        "box",
        "kde",
        "area",
        "pie",
        "hexbin",
    ]
    disclaimer = """
    All models are for educational purposes only. The techniques available in this menu are not
    fine tuned or guaranteed to work. Backtesting results is not a guarantee of future accuracy.
    Investing involves monetary risk that OpenBB does not take a role in. Please research any
    prediction techniques fully before attempting to use. OpenBB is not liable for any loss or
    damages."""

    PATH = "/forecast/"
    CHOICES_GENERATION = True

    loaded_dataset_cols = "\n"
    list_dataset_cols: list = list()

    def __init__(
        self, ticker: str = "", data: pd.DataFrame = empty_df, queue: List[str] = None
    ):
        """Constructor"""
        super().__init__(queue)
        self.files: List[str] = []
        self.choices = {}
        # The full file name with extension, this allows the rest command to work
        self.files_full: List[List[str]] = []
        self.datasets: Dict[str, pd.DataFrame] = dict()
        self.MINIMUM_DATA_LENGTH = 100

        if ticker and not data.empty:
            data = data.reset_index()
            data.columns = data.columns.map(lambda x: x.lower().replace(" ", "_"))

            self.files.append(ticker)
            self.datasets[ticker] = data
            self.loaded_dataset_cols = "\n"

            self.loaded_dataset_cols += (
                f"  {ticker} {(20 - len(ticker)) * ' '}: "
                f"{', '.join(data.columns)}\n"
            )

            for col in data.columns:
                self.list_dataset_cols.append(f"{ticker}.{col}")

        self.DATA_TYPES: List[str] = ["int", "float", "str", "bool", "category", "date"]

        self.signs: Dict[Any, Any] = {
            "div": "/",
            "mul": "*",
            "add": "+",
            "sub": "-",
            "mod": "%",
            "pow": "**",
        }
        self.DATA_FILES = forecast_model.get_default_files()

        # setting device on GPU if available, else CPU
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.comp_ram = f"{round(psutil.virtual_memory().total / (1024.0 ** 3),2)}G"
        self.rec_data_size = f"{(psutil.virtual_memory().total / (1024.0 ** 3))//3}G"
        self.torch_version = torch.__version__
        self.darts_version = darts.__version__

        if session and obbff.USE_PROMPT_TOOLKIT:
            choices: dict = self.choices_default

            self.choices = choices
            self.completer = NestedCompleter.from_nested_dict(choices)

    def call_exit(self, _) -> None:
        """Process exit terminal command from forecast menu."""
        self.save_class()
        for _ in range(self.PATH.count("/") + 1):
            self.queue.insert(0, "quit")

    def get_dataset_columns(self):
        return {
            f"{dataset}.{column}": {column: None, dataset: None}
            for dataset, dataframe in self.datasets.items()
            for column in dataframe.columns
        }

    def update_runtime_choices(self):
        # Load in any newly exported files
        self.DATA_FILES = forecast_model.get_default_files()
        if session and obbff.USE_PROMPT_TOOLKIT:
            choices: dict = self.choices_default

            self.choices = choices
            self.completer = NestedCompleter.from_nested_dict(choices)

    def refresh_datasets_on_menu(self):
        """Refresh datasets on menu with new columns when adding new features"""

        self.list_dataset_cols = list()
        maxfile = max(len(file) for file in self.files)
        self.loaded_dataset_cols = "\n"
        for dataset, data in self.datasets.items():
            self.loaded_dataset_cols += (
                f"  {dataset} {(maxfile - len(dataset)) * ' '}: "
                f"{', '.join(data.columns)}\n"
            )

            for col in data.columns:
                self.list_dataset_cols.append(f"{dataset}.{col}")

    def print_help(self):
        """Print help"""
        mt = MenuText("forecast/")
        mt.add_param("_disclaimer_", self.disclaimer)
        mt.add_raw("\n")
        mt.add_param(
            "_data_loc",
            f"\n\t{USER_EXPORTS_DIRECTORY}\n\t{USER_CUSTOM_IMPORTS_DIRECTORY}",
        )
        mt.add_raw("\n")
        mt.add_cmd("load")
        mt.add_raw("\n")
        mt.add_param("_loaded", self.loaded_dataset_cols)
        mt.add_info("_exploration_")
        mt.add_cmd("show", self.files)
        mt.add_cmd("plot", self.files)
        mt.add_cmd("clean", self.files)
        mt.add_cmd("combine", self.files)
        mt.add_cmd("desc", self.files)
        mt.add_cmd("corr", self.files)
        mt.add_cmd("season", self.files)
        mt.add_cmd("delete", self.files)
        mt.add_cmd("rename", self.files)
        mt.add_cmd("export", self.files)
        mt.add_raw("\n")
        mt.add_info("_feateng_")
        mt.add_cmd("ema", self.files)
        mt.add_cmd("sto", self.files)
        mt.add_cmd("rsi", self.files)
        mt.add_cmd("roc", self.files)
        mt.add_cmd("mom", self.files)
        mt.add_cmd("delta", self.files)
        mt.add_cmd("atr", self.files)
        mt.add_cmd("signal", self.files)
        mt.add_raw("\n")
        mt.add_info("_tsforecasting_")
        mt.add_cmd("autoselect", self.files)
        mt.add_cmd("autoarima", self.files)
        mt.add_cmd("autoces", self.files)
        mt.add_cmd("autoets", self.files)
        mt.add_cmd("mstl", self.files)
        mt.add_cmd("rwd", self.files)
        mt.add_cmd("seasonalnaive", self.files)
        mt.add_cmd("expo", self.files)
        mt.add_cmd("theta", self.files)
        mt.add_cmd("linregr", self.files)
        mt.add_cmd("regr", self.files)
        mt.add_cmd("rnn", self.files)
        mt.add_cmd("brnn", self.files)
        mt.add_cmd("nbeats", self.files)
        mt.add_cmd("nhits", self.files)
        mt.add_cmd("tcn", self.files)
        mt.add_cmd("trans", self.files)
        mt.add_cmd("tft", self.files)

        console.print(text=mt.menu_text, menu="Forecast")

    def custom_reset(self):
        """Class specific component of reset command"""
        if self.files_full:
            queue = ["forecast"]
            for file, alias in self.files_full:
                load = f"'load {file}"
                if alias:
                    load += f" -a {alias}'"
                else:
                    load += "'"
                queue.append(load)
            return queue
        return []

    def add_standard_args(
        self,
        parser: argparse.ArgumentParser,
        target_dataset: bool = False,
        target_column: bool = False,
        period: Optional[int] = None,
        n_days: bool = False,
        seasonal: Optional[str] = None,
        periods: bool = False,
        window: bool = False,
        train_split: bool = False,
        input_chunk_length: bool = False,
        output_chunk_length: bool = False,
        force_reset: bool = False,
        save_checkpoints: bool = False,
        model_save_name: Optional[str] = None,
        n_epochs: bool = False,
        model_type: bool = False,
        dropout: Optional[float] = None,
        batch_size: Optional[int] = None,
        learning_rate: bool = False,
        past_covariates: bool = False,
        all_past_covariates: bool = False,
        lags: bool = False,
        hidden_size: int = 0,
        n_jumps: bool = False,
        end: bool = False,
        start: bool = False,
        residuals: bool = False,
        forecast_only: bool = False,
        naive: bool = False,
        explainability_raw: bool = False,
        export_pred_raw: bool = False,
    ):
        if hidden_size:
            parser.add_argument(
                "--hidden-size",
                action="store",
                dest="hidden_size",
                default=hidden_size,
                type=check_positive,
                help="Size for feature maps for each hidden RNN layer (h_n)",
            )
        if past_covariates:
            parser.add_argument(
                "--past-covariates",
                action="store",
                dest="past_covariates",
                default=None,
                type=str,
                help="Past covariates(columns/features) in same dataset. Comma separated.",
            )
        if all_past_covariates:
            parser.add_argument(
                "--all-past-covariates",
                action="store_true",
                dest="all_past_covariates",
                default=False,
                help="Adds all rows as past covariates except for date and the target column.",
            )
        if naive:
            parser.add_argument(
                "--naive",
                action="store_true",
                dest="naive",
                default=False,
                help="Show the naive baseline for a model.",
            )
        if target_dataset:
            parser.add_argument(
                "-d",
                "--dataset",
                help="The name of the dataset you want to select",
                dest="target_dataset",
                type=str,
                choices=list(self.datasets.keys()),
            )
        if target_column:
            parser.add_argument(
                "-c",
                "--target-column",
                help="The name of the specific column you want to use",
                dest="target_column",
                type=str,
                default="close",
            )
        if period is not None:
            parser.add_argument(
                "--period",
                help="The period to use",
                dest="period",
                type=check_greater_than_one,
                default=period,
            )
        if n_days:
            parser.add_argument(
                "-n",
                "--n-days",
                action="store",
                dest="n_days",
                type=check_positive,
                default=5,
                help="prediction days.",
            )
        if seasonal is not None:
            parser.add_argument(
                "-s",
                "--seasonal",
                action="store",
                dest="seasonal",
                choices=expo_model.SEASONS,
                default=seasonal,
                help="Seasonality: N: None, A: Additive, M: Multiplicative.",
            )
        if periods:
            parser.add_argument(
                "-p",
                "--periods",
                action="store",
                dest="seasonal_periods",
                type=check_positive,
                default=7,
                help="Seasonal periods: 4: Quarterly, 7: Daily",
            )
        if window:
            parser.add_argument(
                "-w",
                "--window",
                action="store",
                dest="start_window",
                default=0.85,
                type=check_positive_float,
                help="Start point for rolling training and forecast window. 0.0-1.0",
            )
        if train_split:
            parser.add_argument(
                "-t",
                "--train-split",
                action="store",
                dest="train_split",
                default=0.85,
                type=check_positive_float,
                help="Start point for rolling training and forecast window. 0.0-1.0",
            )
        if input_chunk_length:
            parser.add_argument(
                "-i",
                "--input-chunk-length",
                action="store",
                dest="input_chunk_length",
                default=14,
                type=check_positive,
                help="Number of past time steps for forecasting module at prediction time.",
            )
        if output_chunk_length:
            parser.add_argument(
                "-o",
                "--output-chunk-length",
                action="store",
                dest="output_chunk_length",
                default=5,
                type=check_positive,
                help="The length of the forecast of the model.",
            )
        if force_reset:
            parser.add_argument(
                "--force-reset",
                action="store",
                dest="force_reset",
                default=True,
                type=bool,
                help="""If set to True, any previously-existing model with the same name will be reset
                        (all checkpoints will be discarded).""",
            )
        if save_checkpoints:
            parser.add_argument(
                "--save-checkpoints",
                action="store",
                dest="save_checkpoints",
                default=True,
                type=bool,
                help="Whether to automatically save the untrained model and checkpoints.",
            )
        if model_save_name is not None:
            parser.add_argument(
                "--model-save-name",
                type=str,
                action="store",
                dest="model_save_name",
                default=model_save_name,
                help="Name of the model to save.",
            )
        if n_epochs:
            parser.add_argument(
                "--n-epochs",
                action="store",
                dest="n_epochs",
                default=300,
                type=check_positive,
                help="Number of epochs over which to train the model.",
            )
        if model_type:
            parser.add_argument(
                "--model-type",
                type=str,
                action="store",
                dest="model_type",
                default="LSTM",
                help='Either a string specifying the RNN module type ("RNN", "LSTM" or "GRU")',
            )
        if dropout is not None:
            parser.add_argument(
                "--dropout",
                action="store",
                dest="dropout",
                default=dropout,
                type=check_positive_float,
                help="Fraction of neurons affected by Dropout, from 0 to 1.",
            )
        if batch_size is not None:
            parser.add_argument(
                "--batch-size",
                action="store",
                dest="batch_size",
                default=batch_size,
                type=check_positive,
                help="Number of time series (input and output) used in each training pass",
            )
        if end:
            parser.add_argument(
                "--end",
                action="store",
                type=valid_date,
                dest="s_end_date",
                default=None,
                help="The end date (format YYYY-MM-DD) to select for testing",
            )
        if start:
            parser.add_argument(
                "--start",
                action="store",
                type=valid_date,
                dest="s_start_date",
                default=None,
                help="The start date (format YYYY-MM-DD) to select for testing",
            )
        if learning_rate:
            parser.add_argument(
                "--learning-rate",
                action="store",
                dest="learning_rate",
                default=1e-3,
                type=check_positive_float,
                help="Learning rate during training.",
            )
        if n_jumps:
            parser.add_argument(
                "-j",
                "--jumps",
                action="store",
                dest="n_jumps",
                type=check_positive,
                default=1,
                help="number of jumps in training data.",
            )
        if lags:
            parser.add_argument(
                "--lags",
                action="store",
                dest="lags",
                type=check_greater_than_one,
                default=14,
                help="Lagged target values used to predict the next time step.",
            )
        if residuals:
            parser.add_argument(
                "--residuals",
                help="Show the residuals for the model.",
                action="store_true",
                default=False,
                dest="residuals",
            )
        if forecast_only:
            parser.add_argument(
                "--forecast-only",
                help="Do not plot the historical data without forecasts.",
                action="store_true",
                default=False,
                dest="forecast_only",
            )
        if explainability_raw:
            parser.add_argument(
                "--explainability-raw",
                action="store_true",
                dest="explainability_raw",
                default=False,
                help="Prints out a raw dataframe showing explainability results.",
            )

        if export_pred_raw:
            parser.add_argument(
                "--export-pred-raw",
                action="store_true",
                dest="export_pred_raw",
                default=False,
                help="Export predictions to a csv file.",
            )

            # if user does not put in --dataset
        return parser

    def load(self, ticker: str, data: pd.DataFrame):
        """Loads news dataframes into memory"""

        # check if data has minimum number of rows
        if len(data) < self.MINIMUM_DATA_LENGTH:
            console.print(
                f"[red]Dataset is smaller than recommended minimum {self.MINIMUM_DATA_LENGTH} data points. [/red]"
            )
            console.print(
                f"[red]Please increase the number of data points for [ {ticker} ] and try again.[/red]"
            )
            return

        if not data.empty:
            data.columns = data.columns.map(lambda x: x.lower().replace(" ", "_"))

            # If the index is a date, move this into a normal column
            if data.index.name == "date":
                data = data.reset_index()
            # Convert date to datetime
            # TODO: for now we drop time, once we add handling for time remove this
            if "date" in data.columns:
                data["date"] = data["date"].apply(helpers.dt_format)
                data["date"] = pd.to_datetime(data["date"])

            # if we import a custom dataset, remove the old index "unnamed:_0"
            if "unnamed:_0" in data.columns:
                data = data.drop(columns=["unnamed:_0"])

            self.files.append(ticker)
            self.datasets[ticker] = data

            # Process new datasets to be updated
            self.list_dataset_cols = list()
            maxfile = max(len(file) for file in self.files)
            self.loaded_dataset_cols = "\n"
            for dataset, x_data in self.datasets.items():
                self.loaded_dataset_cols += (
                    f"  {dataset} {(maxfile - len(dataset)) * ' '}: "
                    f"{', '.join(x_data.columns)}\n"
                )

                for col in data.columns:
                    self.list_dataset_cols.append(f"{dataset}.{col}")

    @log_start_end(log=logger)
    def call_load(self, other_args: List[str]):
        """Process load"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="load",
            description="Load custom dataset (from previous export, custom imports).",
        )
        parser.add_argument(
            "-f",
            "--file",
            help="File to load data in (can be custom import, may have been exported before.)",
            choices=self.DATA_FILES.keys(),
            type=str,
        )
        parser.add_argument(
            "-a",
            "--alias",
            help="Alias name to give to the dataset",
            type=str,
        )
        # Load in any newly exported files
        self.DATA_FILES = forecast_model.get_default_files()

        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-f")
        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if ns_parser:
            if ns_parser.file:
                file = ns_parser.file
                if ns_parser.alias:
                    alias = ns_parser.alias
                else:
                    if "." in ns_parser.file:
                        alias = ".".join(ns_parser.file.split(".")[:-1])
                    else:
                        alias = ns_parser.file

                # check if this dataset has been added already
                if alias in self.files:
                    console.print(
                        "[red]The file/dataset selected has already been loaded.[/red]\n"
                    )
                    return
                data = common_model.load(file, self.DATA_FILES, {})
                if not data.empty:
                    self.files_full.append([ns_parser.file, ns_parser.alias])
                    self.load(alias, data)
            self.update_runtime_choices()

    # Show selected dataframe on console
    @log_start_end(log=logger)
    def call_which(self, other_args: List[str]):
        """Process which command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="which",
            description="Show library versions of required packages.",
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
        )

        if ns_parser:

            console.print(
                f"[green]Current Compute Device (CPU or GPU):[/green] {self.device.upper()}"
            )
            console.print(f"[green]Current RAM:[/green] {self.comp_ram}")
            console.print(
                f"[green]Recommended Max dataset size based on current RAM:[/green] {self.rec_data_size}"
            )
            console.print(f"[green]Torch version:[/green] {self.torch_version}")
            console.print(f"[green]Darts version:[/green] {self.darts_version}")

    # Show selected dataframe on console
    @log_start_end(log=logger)
    def call_show(self, other_args: List[str]):
        """Process show command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="show",
            description="Show a portion of the DataFrame",
        )
        parser.add_argument(
            "-n",
            "--name",
            type=str,
            choices=self.files,
            dest="name",
            help="The name of the database you want to show data for",
        )
        parser.add_argument(
            "-s",
            "--sortby",
            help="Sort based on a column in the DataFrame",
            nargs="+",
            type=str,
            dest="sortby",
            default="",
        )
        parser.add_argument(
            "-r",
            "--reverse",
            action="store_true",
            dest="reverse",
            default=False,
            help=(
                "Data is sorted in descending order by default. "
                "Reverse flag will sort it in an ascending way. "
                "Only works when raw data is displayed."
            ),
        )
        parser.add_argument(
            "--limit-col",
            action="store",
            dest="limit_col",
            default=10,
            type=check_positive,
            help="Set the number of columns to display when showing the dataset",
        )

        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-n")
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            EXPORT_ONLY_RAW_DATA_ALLOWED,
            limit=10,
        )

        if ns_parser:
            if not ns_parser.name:
                dataset_names = list(self.datasets.keys())
            else:
                dataset_names = [ns_parser.name]

            for name in dataset_names:
                df = self.datasets[name]

                if name in self.datasets and self.datasets[name].empty:
                    console.print(
                        f"[red]No data available for {ns_parser.name}.[/red]\n"
                    )
                elif ns_parser.sortby:
                    sort_column = " ".join(ns_parser.sortby)
                    if sort_column not in self.datasets[name].columns:
                        console.print(
                            f"[red]{sort_column} not a valid column."
                            "Showing without sorting.\n[/red]"
                        )
                    else:
                        df = df.sort_values(by=sort_column, ascending=ns_parser.reverse)

                forecast_view.show_df(
                    df, ns_parser.limit, ns_parser.limit_col, name, ns_parser.export
                )

    @log_start_end(log=logger)
    def call_rename(self, other_args: List[str]):
        """Process rename"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rename",
            description="The column you want to rename from a dataset.",
        )
        parser.add_argument(
            "-d",
            "--dataset",
            help="Dataset that will get a column renamed",
            dest="dataset",
            choices=self.datasets.keys(),
            type=str,
        )
        parser.add_argument(
            "-o",
            "--oldcol",
            help="Old column from dataset to be renamed",
            dest="oldcol",
            type=str,
            required="-h" not in other_args,
        )
        parser.add_argument(
            "-n",
            "--newcol",
            help="New column from dataset to be renamed",
            dest="newcol",
            type=str,
            required="-h" not in other_args,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-d")
        ns_parser = self.parse_known_args_and_warn(parser, other_args, NO_EXPORT)

        if ns_parser:
            dataset = ns_parser.dataset
            column_old = ns_parser.oldcol
            column_new = ns_parser.newcol

            if dataset not in self.datasets:
                console.print(
                    f"Not able to find the dataset {dataset}. Please choose one of "
                    f"the following: {', '.join(self.datasets)}"
                )
            else:
                self.datasets[dataset] = forecast_model.rename_column(
                    self.datasets[dataset], column_old, column_new
                )
            self.update_runtime_choices()
            self.refresh_datasets_on_menu()

            console.print(
                f"[green]Successfully renamed {column_old} into {column_new}, in {dataset}[/green]"
            )

    # Show selected dataframe on console
    @log_start_end(log=logger)
    def call_desc(self, other_args: List[str]):
        """Process descriptive stats command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="desc",
            description="Show descriptive statistics of a dataset",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            EXPORT_ONLY_RAW_DATA_ALLOWED,
        )

        if ns_parser:
            # check proper file name is provided
            if not ns_parser.target_dataset:
                console.print("[red]Please enter valid dataset.\n[/red]")
                return

            df = self.datasets[ns_parser.target_dataset]
            forecast_view.describe_df(df, ns_parser.target_dataset, ns_parser.export)

    @log_start_end(log=logger)
    def call_plot(self, other_args: List[str]):
        """Process plot command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="plot",
            description="Plot data based on the index",
        )
        parser.add_argument(
            "-v",
            "--values",
            help="Dataset.column values to be displayed in a plot. Use comma to separate multiple",
            choices=self.get_dataset_columns(),
            dest="values",
            type=str,
        )

        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-v")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
        )
        if not ns_parser:
            return

        if not ns_parser.values:
            console.print("[red]Please enter valid dataset.\n[/red]")
            return

        values = [x.strip() for x in ns_parser.values.split(",")]
        target_df = values[0].split(".")[0]
        if target_df not in self.datasets:
            console.print("[red]Please enter valid dataset.\n[/red]")
            return

        for value in values:
            if value.split(".")[0] != target_df:
                console.print("[red]Please enter values from the same dataset.\n[/red]")
                return

        forecast_view.display_plot(
            self.datasets[target_df],
            [x.split(".")[1] for x in values],
            ns_parser.export,
        )

    @log_start_end(log=logger)
    def call_season(self, other_args: List[str]):
        """Process season command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="season",
            description="The seasonality for a given column",
        )
        parser.add_argument(
            "-v",
            "--values",
            help="Dataset.column values to be displayed in a plot",
            dest="values",
            choices=self.get_dataset_columns(),
            type=str,
        )
        parser.add_argument(
            "-m",
            help="A time lag to highlight on the plot",
            dest="m",
            type=int,
            default=None,
        )
        parser.add_argument(
            "--max_lag",
            help="The maximal lag order to consider",
            dest="max_lag",
            type=int,
            default=24,
        )
        parser.add_argument(
            "-a",
            "--alpha",
            help="The confidence interval to display",
            dest="alpha",
            type=float,
            default=0.05,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-v")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
        )

        if not ns_parser:
            return

        if not ns_parser.values:
            console.print("[red]Please enter valid dataset.\n[/red]")
            return

        try:
            dataset, col = ns_parser.values.split(".")
            data = self.datasets[dataset]
        except ValueError:
            console.print("[red]Please enter 'dataset'.'column'.[/red]\n")
            return

        forecast_view.display_seasonality(
            data=data,
            column=col,
            export=ns_parser.export,
            m=ns_parser.m,
            max_lag=ns_parser.max_lag,
            alpha=ns_parser.alpha,
        )

    @log_start_end(log=logger)
    def call_corr(self, other_args: List[str]):
        """Process correlation command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="corr",
            description="Plot correlation coefficients.",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            # check proper file name is provided
            if not ns_parser.target_dataset:
                console.print("[red]Please enter valid dataset.\n[/red]")
                return

            data = self.datasets[ns_parser.target_dataset]

            forecast_view.display_corr(
                data,
                ns_parser.export,
            )

    @log_start_end(log=logger)
    def call_combine(self, other_args: List[str]):
        """Process combine"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="combine",
            description="Combine two entire datasets, or add specific columns. Add specific"
            "columns with the syntax: <datasetX.column2>",
        )
        parser.add_argument(
            "--dataset",
            help="Dataset to add columns to",
            dest="dataset",
            choices=list(self.datasets.keys()),
        )
        # pylint: disable=consider-using-dict-items
        column_choices = [
            f"{x}.{y}" for x in self.datasets for y in self.datasets[x].columns
        ]
        column_choices.extend(list(self.datasets))
        parser.add_argument(
            "-c",
            "--columns",
            help="The columns we want to add <dataset.column>",
            dest="columns",
            choices=column_choices,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")
        ns_parser = self.parse_known_args_and_warn(parser, other_args, NO_EXPORT)

        if ns_parser:
            if ns_parser.dataset not in self.datasets:
                console.print(
                    f"Not able to find the dataset {ns_parser.dataset}. Please choose one of "
                    f"the following: {', '.join(self.datasets)}"
                )
                return

            data = self.datasets[ns_parser.dataset]

            for option in ns_parser.columns.split(","):
                if "." in option:
                    dataset, col = option.split(".")
                    columns = [col]
                else:
                    dataset = option
                    columns = [x for x in self.datasets[dataset].columns if x != "date"]

                if dataset not in self.datasets:
                    console.print(
                        f"Not able to find the dataset {dataset}. Please choose one of "
                        f"the following: {', '.join(self.datasets)}"
                    )
                    continue
                for column in columns:
                    data = forecast_model.combine_dfs(
                        data, self.datasets[dataset], column, dataset
                    )

            self.datasets[ns_parser.dataset] = data
            self.update_runtime_choices()
            self.refresh_datasets_on_menu()

            console.print(
                f"[green]Successfully added {ns_parser.columns} into {ns_parser.dataset}[/green]"
            )

    @log_start_end(log=logger)
    def call_clean(self, other_args: List[str]):
        """Process clean"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="clean",
            description="Clean a dataset by filling and dropping NaN values.",
        )
        parser.add_argument(
            "-f",
            "--fill",
            help="The method of filling NaNs. This has options to fill rows (rfill, rbfill, rffill) or fill "
            "columns (cfill, cbfill, cffill). Furthermore, it has the option to forward fill and backward fill "
            "(up to --limit) which refer to how many rows/columns can be set equal to the last non-NaN value",
            dest="fill",
            choices=["rfill", "cfill", "rbfill", "cbfill", "rffill", "bffill"],
            default="",
        )
        parser.add_argument(
            "--drop",
            help="The method of dropping NaNs. This either has the option rdrop (drop rows that contain NaNs) "
            "or cdrop (drop columns that contain NaNs)",
            dest="drop",
            choices=["rdrop", "cdrop"],
            default="",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
            limit=5,
        )
        if ns_parser:
            # check proper file name is provided
            if not ns_parser.target_dataset:
                console.print("[red]Please enter valid dataset.\n[/red]")
                return

            (
                self.datasets[ns_parser.target_dataset],
                clean_status,
            ) = forecast_model.clean(
                self.datasets[ns_parser.target_dataset],
                ns_parser.fill,
                ns_parser.drop,
                ns_parser.limit,
            )
            if not clean_status:
                console.print(
                    f"[green]Successfully cleaned '{ns_parser.target_dataset}' dataset[/green]"
                )
            else:
                console.print(f"[red]{ns_parser.name} still contains NaNs.[/red]")

    @log_start_end(log=logger)
    def call_ema(self, other_args: List[str]):
        """Process EMA"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="ema",
            description="Add exponential moving average to dataset based on specific column.",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser, target_dataset=True, period=10, target_column=True
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_ema(
                self.datasets[ns_parser.target_dataset],
                ns_parser.target_column,
                ns_parser.period,
            )
            console.print(
                f"Successfully added 'EMA_{ns_parser.period}' to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_sto(self, other_args: List[str]):
        """Process Stochastic Oscillator"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="sto",
            description="Add in Stochastic Oscillator %K and %D",
        )
        parser.add_argument(
            "--close-col",
            help="Close column name to use for Stochastic Oscillator",
            dest="close_col",
            type=str,
            default="close",
        )
        parser.add_argument(
            "--high-col",
            help="High column name to use for Stochastic Oscillator",
            dest="high_col",
            type=str,
            default="high",
        )
        parser.add_argument(
            "--low-col",
            help="Low column name to use for Stochastic Oscillator",
            dest="low_col",
            type=str,
            default="low",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True, period=10)
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, NO_EXPORT, limit=5
        )
        if ns_parser:
            # check proper file name is provided
            if not ns_parser.target_dataset:
                console.print("[red]Please enter valid dataset.\n[/red]")
                return

            df = forecast_model.add_sto(
                self.datasets[ns_parser.target_dataset],
                close_column=ns_parser.close_col,
                high_column=ns_parser.high_col,
                low_column=ns_parser.low_col,
                period=ns_parser.period,
            )
            if not df.empty:
                self.datasets[ns_parser.target_dataset] = df
                console.print(
                    f"Successfully added 'STOCH&D_{ns_parser.period}' to '{ns_parser.target_dataset}' dataset"
                )
                # update forecast menu with new column on modified dataset
                self.refresh_datasets_on_menu()
                self.update_runtime_choices()

    def handle_delete(self, dataset, column):
        if dataset not in self.datasets:
            console.print(
                f"Not able to find the dataset {dataset}. Please choose one of "
                f"the following: {', '.join(self.datasets)}"
            )
        else:
            forecast_model.delete_column(self.datasets[dataset], column)

    @log_start_end(log=logger)
    def call_delete(self, other_args: List[str]):
        """Process delete"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="delete",
            description="The column you want to delete from a dataset.",
        )
        parser.add_argument(
            "--delete",
            help="The columns you want to delete from a dataset. Use format: <dataset.column> or"
            " multiple with <dataset.column>,<datasetb.column2>",
            dest="delete",
            choices=self.get_dataset_columns(),
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--delete")
        ns_parser = self.parse_known_args_and_warn(parser, other_args, NO_EXPORT)

        if ns_parser:
            if "," in ns_parser.delete:
                for option in ns_parser.delete:
                    print(option)
                    dataset, column = option.split(".")
                    self.handle_delete(dataset, column)
            else:
                dataset, column = ns_parser.delete.split(".")
                self.handle_delete(dataset, column)

            self.update_runtime_choices()
            self.refresh_datasets_on_menu()

            console.print(f"[green]Successfully deleted {ns_parser.delete}[/green]")

    @log_start_end(log=logger)
    def call_rsi(self, other_args: List[str]):
        """Process RSI"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rsi",
            description="Add rsi to dataset based on specific column.",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser, target_dataset=True, target_column=True, period=10
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not ns_parser.target_dataset:
                console.print("[red]Please enter valid dataset.\n[/red]")
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_rsi(
                self.datasets[ns_parser.target_dataset],
                ns_parser.target_column,
                ns_parser.period,
            )
            console.print(
                f"Successfully added 'RSI_{ns_parser.period}_{ns_parser.target_column}' "
                f"to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_roc(self, other_args: List[str]):
        """Process ROC"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="roc",
            description="Add rate of change to dataset based on specific column.",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser, target_dataset=True, target_column=True, period=10
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_roc(
                self.datasets[ns_parser.target_dataset],
                ns_parser.target_column,
                ns_parser.period,
            )
            console.print(
                f"Successfully added 'ROC_{ns_parser.period}' to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_mom(self, other_args: List[str]):
        """Process Momentum"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="mom",
            description="Add momentum to dataset based on specific column.",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser, target_dataset=True, target_column=True, period=10
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_momentum(
                self.datasets[ns_parser.target_dataset],
                ns_parser.target_column,
                ns_parser.period,
            )
            console.print(
                f"Successfully added 'Momentum_{ns_parser.period}' to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_delta(self, other_args: List[str]):
        """Process %Change (Delta)"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="delta",
            description="Add %Change (Delta) to dataset based on specific column.",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True, target_column=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_delta(
                self.datasets[ns_parser.target_dataset], ns_parser.target_column
            )
            console.print(
                f"Successfully added 'Delta_{ns_parser.target_column}' to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_atr(self, other_args: List[str]):
        """Process Average True Range"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="atr",
            description="Add Average True Range to dataset of specific stock ticker.",
        )
        parser.add_argument(
            "--close-col",
            help="Close column name to use for Average True Range.",
            dest="close_col",
            type=str,
            default="close",
        )
        parser.add_argument(
            "--high-col",
            help="High column name to use for Average True Range.",
            dest="high_col",
            type=str,
            default="high",
        )
        parser.add_argument(
            "--low-col",
            help="Low column name to use for Average True Range.",
            dest="low_col",
            type=str,
            default="low",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True, target_column=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_atr(
                self.datasets[ns_parser.target_dataset],
                close_column=ns_parser.close_col,
                high_column=ns_parser.high_col,
                low_column=ns_parser.low_col,
            )
            # check if true range was added
            if "true_range" in self.datasets[ns_parser.target_dataset].columns:
                console.print(
                    f"Successfully added 'Average True Range' to '{ns_parser.target_dataset}' dataset"
                )
            else:
                console.print(
                    "Could not add 'Average True Range' as it does not have one/all specific columns (low/close/high)"
                )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_signal(self, other_args: List[str]):
        """Process Price Signal"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="signal",
            description="""Add price signal to dataset based on closing price.
            1 if the signal is that short term price will go up as compared to the long term.
            0 if the signal is that short term price will go down as compared to the long term.
            """,
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True, target_column=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            NO_EXPORT,
        )
        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            self.datasets[ns_parser.target_dataset] = forecast_model.add_signal(
                self.datasets[ns_parser.target_dataset],
                target_column=ns_parser.target_column,
            )
            console.print(
                f"Successfully added 'Price Signal' to '{ns_parser.target_dataset}' dataset"
            )

            # update forecast menu with new column on modified dataset
            self.refresh_datasets_on_menu()

            self.update_runtime_choices()

    @log_start_end(log=logger)
    def call_export(self, other_args: List[str]):
        """Process export command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="export",
            description="Export dataset to Excel",
        )
        parser.add_argument(
            "-t",
            "--type",
            help="The file type you wish to export to",
            dest="type",
            choices=common_model.file_types,
            type=str,
            default="xlsx",
        )

        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(parser, target_dataset=True)
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=NO_EXPORT,
        )

        if not helpers.check_parser_input(ns_parser, self.datasets, "ignore_column"):
            return

        forecast_view.export_df(
            self.datasets[ns_parser.target_dataset],
            ns_parser.type,
            ns_parser.target_dataset,
        )

    # Best Statistical Model
    @log_start_end(log=logger)
    def call_autoselect(self, other_args: List[str]):
        """Process autoselect command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="autoselect",
            description="""
                Perform Automatic Statistical Forecast
                (select best statistical model from AutoARIMA, AutoETS, AutoCES, MSTL, ...)
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return
            autoselect_view.display_autoselect_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # AutoARIMA Model
    @log_start_end(log=logger)
    def call_autoarima(self, other_args: List[str]):
        """Process autoarima command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="autoarima",
            description="""
                Perform Automatic ARIMA forecast:
                https://nixtla.github.io/statsforecast/examples/getting_started_with_auto_arima_and_ets.html
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            autoarima_view.display_autoarima_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # AutoCES Model
    @log_start_end(log=logger)
    def call_autoces(self, other_args: List[str]):
        """Process autoces command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="autoces",
            description="""
                Perform Automatic Complex Exponential Smoothing forecast:
                https://nixtla.github.io/statsforecast/models.html#autoces
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            autoces_view.display_autoces_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # AutoETS Model
    @log_start_end(log=logger)
    def call_autoets(self, other_args: List[str]):
        """Process autoets command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="autoets",
            description="""
                Perform Automatic ETS (Error, Trend, Seasonality) forecast:
                https://nixtla.github.io/statsforecast/examples/getting_started_with_auto_arima_and_ets.html
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            autoets_view.display_autoets_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # MSTL Model
    @log_start_end(log=logger)
    def call_mstl(self, other_args: List[str]):
        """Process mstl command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="mstl",
            description="""
                Perform Multiple Seasonalities and Trend using Loess (MSTL) forecast:
                https://nixtla.github.io/statsforecast/examples/multipleseasonalities.html
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            mstl_view.display_mstl_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # RWD Model
    @log_start_end(log=logger)
    def call_rwd(self, other_args: List[str]):
        """Process rwd command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="rwd",
            description="""
                Perform Random Walk with Drift forecast:
                https://nixtla.github.io/statsforecast/models.html#randomwalkwithdrift
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=False,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            rwd_view.display_rwd_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # SeasonalNaive Model
    @log_start_end(log=logger)
    def call_seasonalnaive(self, other_args: List[str]):
        """Process seasonalnaive command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="seasonalnaive",
            description="""
                Perform SeasonalNaive forecasting:
                https://nixtla.github.io/statsforecast/models.html#seasonalnaive
            """,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            seasonalnaive_view.display_seasonalnaive_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    # EXPO Model
    @log_start_end(log=logger)
    def call_expo(self, other_args: List[str]):
        """Process expo command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="expo",
            description="""
                Perform Probabilistic Exponential Smoothing forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.exponential_smoothing.html
            """,
        )
        parser.add_argument(
            "--trend",
            action="store",
            dest="trend",
            choices=expo_model.TRENDS,
            default="A",
            help="Trend: N: None, A: Additive, M: Multiplicative.",
        )
        parser.add_argument(
            "--dampen",
            action="store",
            dest="dampen",
            default="F",
            help="Dampening",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            target_column=True,
            n_days=True,
            seasonal="A",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        # TODO Convert this to multi series
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            expo_view.display_expo_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                trend=ns_parser.trend,
                seasonal=ns_parser.seasonal,
                seasonal_periods=ns_parser.seasonal_periods,
                dampen=ns_parser.dampen,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_theta(self, other_args: List[str]):
        """Process theta command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="theta",
            description="""
                Perform Theta forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.theta.html
            """,
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            n_days=True,
            target_column=True,
            seasonal="M",
            periods=True,
            window=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            theta_view.display_theta_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                seasonal=ns_parser.seasonal,
                seasonal_periods=ns_parser.seasonal_periods,
                start_window=ns_parser.start_window,
                forecast_horizon=ns_parser.n_days,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_rnn(self, other_args: List[str]):
        """Process RNN command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="rnn",
            description="""
                Perform RNN forecast (Vanilla RNN, LSTM, GRU):
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.rnn_model.html
            """,
        )
        # RNN Hyperparameters
        parser.add_argument(
            "--hidden-dim",
            action="store",
            dest="hidden_dim",
            default=20,
            type=check_positive,
            help="Size for feature maps for each hidden RNN layer (h_n)",
        )
        parser.add_argument(
            "--training_length",
            action="store",
            dest="training_length",
            default=20,
            type=check_positive,
            help="""The length of both input (target and covariates) and output (target) time series used during training.
            Generally speaking, training_length should have a higher value than input_chunk_length because otherwise
            during training the RNN is never run for as many iterations as it will during training.""",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            n_days=True,
            target_column=True,
            train_split=True,
            input_chunk_length=True,
            force_reset=True,
            save_checkpoints=True,
            model_save_name="rnn_model",
            n_epochs=True,
            model_type=True,
            dropout=0,
            batch_size=32,
            learning_rate=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            rnn_view.display_rnn_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                model_type=ns_parser.model_type,
                hidden_dim=ns_parser.hidden_dim,
                dropout=ns_parser.dropout,
                batch_size=ns_parser.batch_size,
                n_epochs=ns_parser.n_epochs,
                learning_rate=ns_parser.learning_rate,
                model_save_name=ns_parser.model_save_name,
                training_length=ns_parser.training_length,
                input_chunk_size=ns_parser.input_chunk_length,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_nbeats(self, other_args: List[str]):
        """Process NBEATS command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="nbeats",
            description="""
                Perform NBEATS forecast (Neural Bayesian Estimation of Time Series):
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.nbeats.html
            """,
        )
        # NBEATS Hyperparameters
        parser.add_argument(
            "--num_stacks",
            action="store",
            dest="num_stacks",
            default=10,
            type=check_positive_float,
            help="The number of stacks that make up the whole model.",
        )
        parser.add_argument(
            "--num_blocks",
            action="store",
            dest="num_blocks",
            default=3,
            type=check_positive,
            help="The number of blocks making up every stack.",
        )
        parser.add_argument(
            "--num_layers",
            action="store",
            dest="num_layers",
            default=4,
            type=check_positive,
            help="""The number of fully connected layers preceding the final forking layers
            in each block of every stack.""",
        )
        parser.add_argument(
            "--layer_widths",
            action="store",
            dest="layer_widths",
            default=512,
            type=check_positive,
            help="""Determines the number of neurons that make up each fully connected layer
                in each block of every stack""",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            n_days=True,
            target_column=True,
            train_split=True,
            input_chunk_length=True,
            output_chunk_length=True,
            save_checkpoints=True,
            force_reset=True,
            model_save_name="nbeats_model",
            learning_rate=True,
            n_epochs=True,
            batch_size=800,
            past_covariates=True,
            all_past_covariates=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            nbeats_view.display_nbeats_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                num_stacks=ns_parser.num_stacks,
                num_blocks=ns_parser.num_blocks,
                num_layers=ns_parser.num_layers,
                layer_widths=ns_parser.layer_widths,
                batch_size=ns_parser.batch_size,
                n_epochs=ns_parser.n_epochs,
                learning_rate=ns_parser.learning_rate,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_tcn(self, other_args: List[str]):
        """Process TCN command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="tcn",
            description="""
                Perform TCN forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.tcn_model.html
            """,
        )
        # TCN Hyperparameters
        parser.add_argument(
            "--num-filters",
            action="store",
            dest="num_filters",
            default=3,
            type=check_positive,
            help="The number of filters in a convolutional layer of the TCN",
        )
        parser.add_argument(
            "--weight-norm",
            action="store",
            dest="weight_norm",
            default=True,
            type=bool,
            help="Boolean value indicating whether to use weight normalization.",
        )
        parser.add_argument(
            "--dilation-base",
            action="store",
            dest="dilation_base",
            default=2,
            type=check_positive,
            help="The base of the exponent that will determine the dilation on every level.",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            target_dataset=True,
            n_days=True,
            target_column=True,
            train_split=True,
            past_covariates=True,
            all_past_covariates=True,
            save_checkpoints=True,
            force_reset=True,
            model_save_name="tcn_model",
            learning_rate=True,
            n_epochs=True,
            dropout=0.1,
            batch_size=32,
            input_chunk_length=True,
            output_chunk_length=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            tcn_view.display_tcn_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                dropout=ns_parser.dropout,
                num_filters=ns_parser.num_filters,
                weight_norm=ns_parser.weight_norm,
                dilation_base=ns_parser.dilation_base,
                batch_size=ns_parser.batch_size,
                n_epochs=ns_parser.n_epochs,
                learning_rate=ns_parser.learning_rate,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_regr(self, other_args: List[str]):
        """Process REGR command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="regr",
            description="""
                Perform a regression forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.regression_model.html
            """,
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            output_chunk_length=True,
            train_split=True,
            past_covariates=True,
            all_past_covariates=True,
            n_days=True,
            target_dataset=True,
            target_column=True,
            lags=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            explainability_raw=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            # check proper file name is provided
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            regr_view.display_regression(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                output_chunk_length=ns_parser.output_chunk_length,
                lags=ns_parser.lags,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                explainability_raw=ns_parser.explainability_raw,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_linregr(self, other_args: List[str]):
        """Process LINREGR command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="linregr",
            description="""
                Perform a linear regression forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.linear_regression_model.html
            """,
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            lags=True,
            output_chunk_length=True,
            train_split=True,
            past_covariates=True,
            all_past_covariates=True,
            target_column=True,
            n_days=True,
            target_dataset=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            explainability_raw=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            linregr_view.display_linear_regression(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                output_chunk_length=ns_parser.output_chunk_length,
                lags=ns_parser.lags,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                explainability_raw=ns_parser.explainability_raw,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_brnn(self, other_args: List[str]):
        """Process BRNN command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="brnn",
            description="""
                Perform BRNN forecast (Vanilla RNN, LSTM, GRU):
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.block_rnn_model.html
            """,
        )
        # BRNN Hyperparameters
        parser.add_argument(
            "--n-rnn-layers",
            action="store",
            dest="n_rnn_layers",
            default=1,
            type=check_positive,
            help="Number of layers in the RNN module.",
        )
        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            save_checkpoints=True,
            force_reset=True,
            model_save_name="brnn_model",
            learning_rate=True,
            n_epochs=True,
            batch_size=32,
            dropout=0,
            input_chunk_length=True,
            output_chunk_length=True,
            model_type=True,
            train_split=True,
            past_covariates=True,
            all_past_covariates=True,
            target_dataset=True,
            n_days=True,
            target_column=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            brnn_view.display_brnn_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                model_type=ns_parser.model_type,
                n_rnn_layers=ns_parser.n_rnn_layers,
                dropout=ns_parser.dropout,
                batch_size=ns_parser.batch_size,
                n_epochs=ns_parser.n_epochs,
                learning_rate=ns_parser.learning_rate,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_trans(self, other_args: List[str]):
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="trans",
            description="""
                Perform Transformer Forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.transformer_model.html
            """,
        )
        parser.add_argument(
            "--d-model",
            action="store",
            dest="d_model",
            default=64,
            type=check_positive,
            help="Number of expected features in inputs.",
        )
        parser.add_argument(
            "--nhead",
            action="store",
            dest="nhead",
            default=4,
            type=check_positive,
            help="Number of head in the attention mechanism.",
        )
        parser.add_argument(
            "--num_encoder_layers",
            action="store",
            dest="num_encoder_layers",
            default=3,
            type=check_positive,
            help="The number of encoder layers in the encoder.",
        )
        parser.add_argument(
            "--num_decoder_layers",
            action="store",
            dest="num_decoder_layers",
            default=3,
            type=check_positive,
            help="The number of decoder layers in the encoder.",
        )
        parser.add_argument(
            "--dim_feedforward",
            action="store",
            dest="dim_feedforward",
            default=512,
            type=check_positive,
            help="The dimension of the feedforward model.",
        )
        parser.add_argument(
            "--activation",
            action="store",
            dest="activation",
            default="relu",
            choices=["relu", "gelu"],
            type=str,
            help="Number of LSTM layers.",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            n_days=True,
            target_column=True,
            target_dataset=True,
            past_covariates=True,
            all_past_covariates=True,
            train_split=True,
            input_chunk_length=True,
            output_chunk_length=True,
            dropout=0,
            batch_size=32,
            n_epochs=True,
            model_save_name="trans_model",
            force_reset=True,
            save_checkpoints=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )
        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            trans_view.display_trans_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                d_model=ns_parser.d_model,
                nhead=ns_parser.nhead,
                num_encoder_layers=ns_parser.num_encoder_layers,
                num_decoder_layers=ns_parser.num_decoder_layers,
                dim_feedforward=ns_parser.dim_feedforward,
                activation=ns_parser.activation,
                dropout=ns_parser.dropout,
                n_epochs=ns_parser.n_epochs,
                batch_size=ns_parser.batch_size,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_tft(self, other_args: List[str]):
        """Process TFT command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="tft",
            description="""
                Perform TFT forecast (Temporal Fusion Transformer):
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.tft_model.html
            """,
        )
        parser.add_argument(
            "--lstm-layers",
            action="store",
            dest="lstm_layers",
            default=1,
            type=check_positive,
            help="Number of LSTM layers.",
        )
        parser.add_argument(
            "--num-attention-heads",
            action="store",
            dest="num_attention_heads",
            default=4,
            type=check_positive,
            help="Number of attention heads.",
        )
        parser.add_argument(
            "--full-attention",
            action="store_true",
            dest="full_attention",
            default=False,
            help="Whether to apply a multi-head attention query.",
        )
        parser.add_argument(
            "--hidden-continuous-size",
            action="store",
            dest="hidden_continuous_size",
            default=8,
            type=check_positive,
            help="Default hidden size for processing continuous variables.",
        )

        # if user does not put in --dataset
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            save_checkpoints=True,
            target_dataset=True,
            n_days=True,
            force_reset=True,
            model_save_name="tft_model",
            train_split=True,
            hidden_size=16,
            batch_size=32,
            n_epochs=True,
            dropout=0.1,
            input_chunk_length=True,
            output_chunk_length=True,
            past_covariates=True,
            all_past_covariates=True,
            target_column=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            tft_view.display_tft_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                hidden_size=ns_parser.hidden_size,
                lstm_layers=ns_parser.lstm_layers,
                num_attention_heads=ns_parser.num_attention_heads,
                full_attention=ns_parser.full_attention,
                dropout=ns_parser.dropout,
                hidden_continuous_size=ns_parser.hidden_continuous_size,
                n_epochs=ns_parser.n_epochs,
                batch_size=ns_parser.batch_size,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )

    @log_start_end(log=logger)
    def call_nhits(self, other_args: List[str]):
        """Process nhits command"""
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            add_help=False,
            prog="nhits",
            description="""
                Perform nhits forecast:
                https://unit8co.github.io/darts/generated_api/darts.models.forecasting.tft_model.html
            """,
        )
        parser.add_argument(
            "--num-stacks",
            dest="num_stacks",
            type=check_positive,
            default=3,
            help="The number of stacks that make up the model",
        )
        parser.add_argument(
            "--num-blocks",
            dest="num_blocks",
            type=check_positive,
            default=1,
            help="The number of blocks making up every stack",
        )
        parser.add_argument(
            "--num-layers",
            dest="num_layers",
            type=check_positive,
            default=2,
            help="The number of fully connected layers",
        )
        parser.add_argument(
            "--layer_widths",
            dest="layer_widths",
            type=check_positive,
            default=512,
            help="The number of neurons in each layer",
        )
        parser.add_argument(
            "--activation",
            dest="activation",
            type=str,
            default="ReLU",
            choices=[
                "ReLU",
                "RReLU",
                "PReLU",
                "Softplus",
                "Tanh",
                "SELU",
                "LeakyReLU",
                "Sigmoid",
            ],
            help="The desired activation",
        )
        parser.add_argument(
            "--max_pool_1d",
            action="store_true",
            dest="maxpool1d",
            default=True,
            help="Whether to use max_pool_1d or AvgPool1d",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "--dataset")

        parser = self.add_standard_args(
            parser,
            save_checkpoints=True,
            target_dataset=True,
            n_days=True,
            force_reset=True,
            model_save_name="nhits_model",
            train_split=True,
            dropout=0.1,
            input_chunk_length=True,
            output_chunk_length=True,
            batch_size=32,
            n_epochs=True,
            past_covariates=True,
            all_past_covariates=True,
            target_column=True,
            residuals=True,
            forecast_only=True,
            start=True,
            end=True,
            naive=True,
            export_pred_raw=True,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
        )

        if ns_parser:
            if not helpers.check_parser_input(ns_parser, self.datasets):
                return

            covariates = helpers.clean_covariates(
                ns_parser, self.datasets[ns_parser.target_dataset]
            )

            nhits_view.display_nhits_forecast(
                data=self.datasets[ns_parser.target_dataset],
                dataset_name=ns_parser.target_dataset,
                n_predict=ns_parser.n_days,
                target_column=ns_parser.target_column,
                past_covariates=covariates,
                train_split=ns_parser.train_split,
                forecast_horizon=ns_parser.n_days,
                input_chunk_length=ns_parser.input_chunk_length,
                output_chunk_length=ns_parser.output_chunk_length,
                num_stacks=ns_parser.num_stacks,
                num_blocks=ns_parser.num_blocks,
                num_layers=ns_parser.num_layers,
                layer_widths=ns_parser.layer_widths,
                activation=ns_parser.activation,
                max_pool_1d=ns_parser.maxpool1d,
                batch_size=ns_parser.batch_size,
                n_epochs=ns_parser.n_epochs,
                dropout=ns_parser.dropout,
                model_save_name=ns_parser.model_save_name,
                force_reset=ns_parser.force_reset,
                save_checkpoints=ns_parser.save_checkpoints,
                export=ns_parser.export,
                residuals=ns_parser.residuals,
                forecast_only=ns_parser.forecast_only,
                start_date=ns_parser.s_start_date,
                end_date=ns_parser.s_end_date,
                naive=ns_parser.naive,
                export_pred_raw=ns_parser.export_pred_raw,
            )
