"""Converts an OSeMOSYS solution file from CPLEX, CBC or GLPK into CBC or CSV format

"""
import argparse
from typing import List, Tuple


class ConvertLine(object):
    """Abstract class which defines the interface to the family of convertors

    Inherit this class and implement the ``_do_it()`` method to produce the
    data to be written out into a new format

    Example
    -------
    >>> cplex_line = "AnnualCost	REGION	CDBACKSTOP	1.0	0.0	137958.8400384134"
    >>> convertor = RegionTechnology()
    >>> convertor.convert()
    VariableName(REGION,TECHCODE01,2015)       42.69         0\\n
    VariableName(REGION,TECHCODE01,2017)       137958.84         0\\n
    """

    def __init__(self, data: List, start_year: int, end_year: int, output_format="cbc"):
        self.data = data
        self.start_year = start_year
        self.end_year = end_year
        self.output_format = output_format

    def _do_it(self) -> Tuple:
        raise NotImplementedError()

    def convert(self) -> List[str]:
        if self.output_format == "cbc":
            convert = self.convert_cbc()
        elif self.output_format == "csv":
            convert = self.convert_csv()
        return convert

    def convert_csv(self) -> List[str]:
        """Format the data for writing to a csv file
        """
        data = []
        variable, dimensions, values = self._do_it()

        for index, value in enumerate(values):

            year = self.start_year + index
            if (value not in ["0.0", "0", ""]) and (year <= self.end_year):

                try:
                    value = float(value)
                except ValueError:
                    value = 0

                full_dims = ",".join(dimensions + (str(year),))

                formatted_data = '{0},"{1}",{2}\n'.format(variable, full_dims, value)

                data.append(formatted_data)

        return data

    def convert_cbc(self) -> List[str]:
        """Format the data for writing to a CBC file
        """
        cbc_data = []
        variable, dimensions, values = self._do_it()

        for index, value in enumerate(values):

            year = self.start_year + index
            if (value not in ["0.0", "0", ""]) and (year <= self.end_year):

                try:
                    value = float(value)
                except ValueError:
                    value = 0

                full_dims = ",".join(dimensions + (str(year),))

                formatted_data = "0 {0}({1}) {2} 0\n".format(variable, full_dims, value)

                cbc_data.append(formatted_data)

        return cbc_data


class RegionTimeSliceTechnologyMode(ConvertLine):
    def _do_it(self) -> Tuple:
        """Produces output indexed by Region, Timeslice, Tech and Mode

        ``0 VariableName(REGION,SD1D,TECHCODE01,2,2015) 42.69 0\n``

        """
        variable = self.data[0]
        region = self.data[1]
        timeslice = self.data[2]
        technology = self.data[3]
        mode = self.data[4]
        values = self.data[5:]

        dimensions = (region, timeslice, technology, mode)

        return (variable, dimensions, values)


class RegionTechnology(ConvertLine):
    def _do_it(self) -> Tuple:
        """Produces output indexed by dimensions Region and Technology

        ``0 VariableName(REGION,TECHCODE01,2015) 42.69 0\n``

        """
        variable = self.data[0]
        region = self.data[1]
        technology = self.data[2]

        dimensions = (region, technology)

        values = self.data[3:]

        return (variable, dimensions, values)


def process_line(
    line: str, start_year: int, end_year: int, output_format: str
) -> List[str]:
    """Processes an individual line in a CPLEX file

    A different ConvertLine implementation is chosen depending upon the
    variable name

    Arguments
    ---------
    line: str
    start_year: int
    end_year: int
    output_format: str
        The file format required - either ``csv`` or ``cbc``
    """
    row_as_list = line.split("\t")
    variable = row_as_list[0]
    if variable in [
        "NewCapacity",
        "TotalCapacityAnnual",
        "CapitalInvestment",
        "AnnualFixedOperatingCost",
        "AnnualVariableOperatingCost",
    ]:
        convertor = RegionTechnology(
            row_as_list, start_year, end_year, output_format
        ).convert()
    elif variable in ["RateOfActivity"]:
        convertor = RegionTimeSliceTechnologyMode(
            row_as_list, start_year, end_year, output_format
        ).convert()
    else:
        convertor = []

    return convertor


def convert_cplex_file(
    cplex_filename: str,
    output_filename: str,
    start_year=2015,
    end_year=2070,
    output_format="cbc",
):
    """Converts a CPLEX solution file into that of the CBC solution file

    Arguments
    ---------
    cplex_filename : str
        Path to the transformed CPLEX solution file
    output_filename : str
        Path for the processed data to be written to
    """

    with open(output_filename, "w") as cbc_file:
        with open(cplex_filename, "r") as cplex_file:
            for linenum, line in enumerate(cplex_file):
                try:
                    convertor = process_line(line, start_year, end_year, output_format)
                    if convertor:
                        cbc_file.writelines(convertor)
                except ValueError:
                    msg = "Error caused at line {}: {}"
                    raise ValueError(msg.format(linenum, line))


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Convert OSeMOSYS CPLEX files into different formats"
    )
    parser.add_argument(
        "cplex_file", help="The filepath of the OSeMOSYS cplex output file"
    )
    parser.add_argument(
        "output_file", help="The filepath of the converted file that will be written"
    )
    parser.add_argument(
        "-s",
        "--start_year",
        type=int,
        default=2015,
        help="Output only the results from this year onwards",
    )
    parser.add_argument(
        "-e",
        "--end_year",
        type=int,
        default=2070,
        help="Output only the results upto and including this year",
    )
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--csv",
        action="store_true",
        help="Output file in comma-separated-values format",
    )
    group.add_argument(
        "--cbc", action="store_true", help="Output file in CBC format, (default option)"
    )

    args = parser.parse_args()

    if args.csv:
        output_format = "csv"
    else:
        output_format = "cbc"

    convert_cplex_file(
        args.cplex_file, args.output_file, args.start_year, args.end_year, output_format
    )
