#!/usr/bin/env python3

#
# Copyright 2019 - 2021 by Ben Key
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.
#

import argparse
import configparser
import os.path
import sys

from typing import List, Tuple

class StyleSheetGenerator:
    """A class that is used to generate a stylesheet (CSS file) from a template
    using a theme defined in an INI file."""

    def __init__(
        self, templateFileName: str, themeName: str,
        iniFile: str, outputDirectory: str, cssFileName: str):
        """Constructor of the StyleSheetGenerator class."""
        self.__isValid: bool = True
        self.__invalidMemberVariables: List[str] = list()
        self.__templateFileName: str = templateFileName
        self.__themeName: str = themeName
        self.__iniFile: str = iniFile
        self.__outputDirectory: str = outputDirectory
        self.__cssFileName: str = cssFileName
        if (
            len(templateFileName) == 0
            or not os.path.isfile(templateFileName)
            ):
            self.__isValid = False
            self.__invalidMemberVariables.append("templateFileName")
        if (len(themeName) == 0):
            self.__isValid = False
            self.__invalidMemberVariables.append("themeName")
        if (
            len(iniFile) == 0
            or not os.path.isfile(iniFile)
            ):
            self.__isValid = False
            self.__invalidMemberVariables.append("iniFile")
        if (
            len(outputDirectory) == 0
            or not os.path.isdir(outputDirectory)
            ):
            self.__isValid = False
            self.__invalidMemberVariables.append("outputDirectory")

    def __LoadTemplate(self) -> str:
        contents: str = ""
        with open(self.__templateFileName, 'r') as file:
            contents = file.read()
        return contents

    def __GetBaseNameFromTemplateFileName(self) -> str:
        head, tail = os.path.split(self.__templateFileName)
        indexOfDot: int = tail.index('.')
        return tail[:indexOfDot]

    def __GetStyleSheetPath(self, baseName: str, themeName: str) -> str:
        fileName: str = "%(baseName)s-%(themeName)s.css" % {
            "baseName": baseName,
            "themeName": themeName
        }
        filePath: str = os.path.join(self.__outputDirectory, fileName)
        return filePath

    def __GetCommonItems(
        self, configParser: configparser.ConfigParser,
        baseName: str, themeName: str) -> List[Tuple[str, str]]:
        commonTemplateSectionName: str = "%(baseName)s-Common" % {
            "baseName": baseName
        }
        commonThemeSectionName: str = "Common-%(themeName)s" % {
            "themeName": themeName
        }
        commonItems = []
        if (configParser.has_section("Common")):
            commonItems.extend(configParser.items("Common"))
        if (configParser.has_section(commonTemplateSectionName)):
            commonItems.extend(configParser.items(commonTemplateSectionName))
        if (configParser.has_section(commonThemeSectionName)):
            commonItems.extend(configParser.items(commonThemeSectionName))
        return commonItems

    def __ReadThemeSettings(
        self, baseName: str, themeName: str) -> List[Tuple[str, str]]:
        ret = []
        try:
            configParser = configparser.ConfigParser()
            configParser.optionxform = str # type: ignore
            configParser.read(self.__iniFile)
            vars = {}
            commonSectionItems = self.__GetCommonItems(
                configParser, baseName, themeName)
            if (len(commonSectionItems) != 0):
                vars = dict(commonSectionItems)
            if (configParser.has_section(themeName)):
                ret.extend(configParser.items(themeName, False, vars))
            sectionName: str = "%(baseName)s-%(themeName)s" % {
                "baseName": baseName,
                "themeName": themeName
            }
            if (configParser.has_section(sectionName)):
                ret.extend(configParser.items(sectionName, False, vars))
            if (len(commonSectionItems) != 0):
                ret.extend(commonSectionItems)
            return ret
        except configparser.NoSectionError:
            return ret
        except configparser.DuplicateSectionError:
            return ret
        except configparser.NoOptionError:
            return ret
        except configparser.InterpolationDepthError:
            return ret
        except configparser.InterpolationSyntaxError:
            return ret
        except configparser.InterpolationMissingOptionError:
            return ret
        except configparser.InterpolationError:
            return ret
        except configparser.MissingSectionHeaderError:
            return ret
        except configparser.ParsingError:
            return ret

    def Generate(self) -> bool:
        """The main workhorse of the StyleSheetGenerator class."""
        if (self.__isValid == False):
            print("The following member variables are invalid.")
            print(*self.__invalidMemberVariables, sep = ", ")
            return False
        contents: str = self.__LoadTemplate()
        if (len(contents) == 0):
            print("LoadTemplate failed.")
            return False
        baseName: str = self.__GetBaseNameFromTemplateFileName()
        themePath: str = os.path.join(self.__outputDirectory, self.__cssFileName)
        if (not themePath):
            themePath = self.__GetStyleSheetPath(baseName, self.__themeName)
        print("Generating '%s'." % (themePath))
        themeSettings: List[Tuple[str, str]] = self.__ReadThemeSettings(baseName, self.__themeName)
        if (len(themeSettings) == 0):
            print("ReadThemeSettings failed.")
            return False
        for (name, value) in themeSettings:
            search: str = "${%s}" % (name)
            contents = contents.replace(search, value)
        with open(themePath, 'w') as file:
            file.write(contents)
        return True

def main(argv = None):
    parser = argparse.ArgumentParser(
        description='Generates a CSS file from a template using theme settings obtained from an INI file.')
    parser.add_argument(
        "-c", "--css",
        type=str,
        help="Specify the name of the CSS file. If not specified the name ${baseName}-${themeName}.css will be used where ${baseName} is the base file name of the template file and ${themeName} is specified by the --theme command line argument.")
    parser.add_argument(
        "-i", "--ini",
        type=str, required=True,
        help="Specify the INI file.")
    parser.add_argument(
        "-n", "--theme",
        type=str, required=True,
        help="Specify the theme name.")
    parser.add_argument(
        "-o", "--output",
        type=str,
        help="Specify the output directory. If not specified the directory containing the template file will be used.")
    parser.add_argument(
        "-t", "--template",
        type=str, required=True,
        help="Specify the template file.")
    args = parser.parse_args()
    if (args.output is None and args.template is not None):
        args.output = os.path.dirname(args.template)
    cssFileName = ""
    if (args.css is not None):
        cssFileName = args.css
    generator = StyleSheetGenerator(
        args.template, args.theme, args.ini, args.output, cssFileName)
    if (generator.Generate() == False):
        return 1
    return 0

if (__name__ == "__main__"):
    sys.exit(main(sys.argv))
