#!/usr/bin/env python
"""
Defines the utility that walks the user through plotting a horizontal slice.

Copyright 2017 Southern California Earthquake Center

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# Python Imports
import sys
import hashlib

# Package Imports
import xmltodict

# UCVM Imports
from ucvm.src.framework.ucvm import UCVM
from ucvm.src.shared.functions import ask_and_validate, is_number, is_valid_proj4_string, \
                                      is_acceptable_value
from ucvm.src.shared.constants import UCVM_DEFAULT_PROJECTION, UCVM_DEPTH, UCVM_ELEVATION
from ucvm.src.visualization.horizontal_slice import HorizontalSlice


def usage() -> None:
    """
    Displays the help text associated with this utility.

    Returns:
        Nothing
    """
    UCVM.print_with_replacements(
        "\n"
        "ucvm_plot_horizontal_slice - UCVM Version [version]\n"
        "\n"
        "Extracts and/or plots a horizontal slice through a given velocity model. If no\n"
        "parameters are given, then a series of questions are asked to determine what the user\n"
        "would like to do.\n"
        "\n"
        "-f, --file f:          The configuration file to read from.\n"
        "-a, --advanced:        If this flag is set, additional questions will be asked."
    )


def ask_questions(advanced: bool, test: bool) -> dict:
    """
    Asks the necessary questions to generate the XML configuration file.

    Args:
        advanced (bool): If set to true, advanced questions will be asked.
        test (bool): If set to true, then we are running this from the testing framework.

    Returns:
        The compiled question dictionary if successful or None if not.
    """
    answers = {
        "bottom_left_point": {
            "x": 0,
            "y": 0,
            "z": 0,
            "depth_elev": 0,
            "projection": ""
        },
        "properties": {
            "num_x": 0,
            "num_y": 0,
            "rotation": 0,
            "spacing": 0
        },
        "cvm_list": "",
        "plot": {
            "generate": "",
            "features": {
                "scale": "",
                "colormap": "",
                "faults": "Yes",
                "topography": "No"
            },
            "title": "",
            "property": ""
        },
        "data": {
            "save": "n",
            "location": "",
            "name": ""
        }
    }

    # Get list of installed models.
    installed_models = {}
    counter = 1
    UCVM.get_list_of_installed_models()
    for model in UCVM.get_list_of_installed_models()["velocity"]:
        if model["id"] == "1d" or model["id"] == "dataproductreader":
            continue
        installed_models[counter] = {"name": model["name"], "id": model["id"]}
        counter += 1

    print("Generating a horizontal slice requires various parameters to be defined (such \n"
          "as the origin of the slice, the length of the slice, and so on). The following\n"
          "questions will guide you through the definition of those parameters.")

    if advanced:
        answers["bottom_left_point"]["projection"] = \
            ask_and_validate("\nTo start, in which projection is your starting point specified?\n"
                             "The default for UCVM is WGS84 latitude and longitude. To accept\n"
                             "the default projection, simply hit enter:", is_valid_proj4_string,
                             "The answer must be a valid Proj.4 projection.")
    else:
        answers["bottom_left_point"]["projection"] = ""

    if answers["bottom_left_point"]["projection"].strip() is "":
        answers["bottom_left_point"]["projection"] = UCVM_DEFAULT_PROJECTION

    answers["bottom_left_point"]["x"] = \
        float(ask_and_validate("\nWhat is the X or longitudinal coordinate of your bottom-left "
                               "starting point?", is_number, "Answer must be a number."))
    answers["bottom_left_point"]["y"] = \
        float(ask_and_validate("What is the Y or latitudinal coordinate of your bottom-left "
                               "starting point?", is_number, "Answer must be a number."))
    answers["bottom_left_point"]["z"] = \
        float(ask_and_validate("What is the Z or depth/elevation coordinate of your bottom-left "
                               "starting point?", is_number, "Answer must be a number."))

    answers["bottom_left_point"]["depth_elev"] = \
        ask_and_validate("\nIs your Z coordinate specified as depth (default) or elevation?\n"
                         "Type 'd' or enter for depth, 'e' for elevation:", is_acceptable_value,
                         "Type the character 'd' for depth or 'e' for elevation.",
                         allowed=["d", "e", ""])

    if str(answers["bottom_left_point"]["depth_elev"]).lower().strip() == "e":
        answers["bottom_left_point"]["depth_elev"] = UCVM_ELEVATION
    else:
        answers["bottom_left_point"]["depth_elev"] = UCVM_DEPTH

    answers["properties"]["num_x"] = int(
        ask_and_validate("\nHow many longitudinal (or X-axis) grid points should there be?",
                         is_number)
    )
    answers["properties"]["num_y"] = int(
        ask_and_validate("How many latitudinal (or Y-axis) grid points should there be?",
                         is_number)
    )
    answers["properties"]["spacing"] = float(
        ask_and_validate("What should the spacing between each grid point be?",
                         is_number)
    )

    answers["properties"]["rotation"] = \
        float(ask_and_validate("\nWhat is the rotation angle, in degrees, of this box (relative to"
                               " the bottom-left corner)?", is_number, "Answer must a number."))

    print("\nYou must select the velocity model(s) from which you would like to retrieve this\n"
          "data. You can either enter in your model(s) as text (e.g. cvms4 or dataproductreader[file]) or you\n"
          "can select from one of the predefined ones in the list below.")

    if not test:
        for index, name in installed_models.items():
            print(str(index) + ") " + name["name"])

    answers["cvm_list"] = ask_and_validate("\nWhich velocity model would you like?")

    try:
        int(answers["cvm_list"])
        answers["cvm_list"] = installed_models[int(answers["cvm_list"])]["id"]
    except ValueError:
        pass

    if advanced:
        answers["plot"]["generate"] = ask_and_validate("\nWould you like to plot the extracted "
                                                       "data?\nType 'y' or 'yes' to plot:",
                                                       is_acceptable_value, allowed=["y", "yes"])

        if answers["plot"]["generate"] == "y" or answers["plot"]["generate"] == "yes":
            answers["plot"]["property"] = ask_and_validate("\nWhich property should be plotted?\n"
                                                           "Acceptable answers include Vp, Vs, "
                                                           "density, Qp, or Qs:",
                                                           is_acceptable_value,
                                                           allowed=["vp", "vs", "density",
                                                                    "qp", "qs"], lower=True)
            answers["plot"]["title"] = ask_and_validate("What should the title of the plot be?\n"
                                                        "Hit enter to accept the default title:")
            answers["plot"]["features"]["scale"] = \
                ask_and_validate("Would you like a discrete or smooth color scale?\n"
                                 "Type 'd' for discrete, 's' for smooth:", is_acceptable_value,
                                 allowed=["d", "s"])
            if answers["plot"]["features"]["scale"] == "d":
                answers["plot"]["features"]["scale"] = "discrete"
            else:
                answers["plot"]["features"]["scale"] = "smooth"
            answers["plot"]["features"]["colormap"] = \
                ask_and_validate("What should the type of scale be (in Matplotlib colors)?\n"
                                 "The default is RdBu, which means red-blue:")
            if answers["plot"]["features"]["colormap"].strip() == "":
                answers["plot"]["features"]["colormap"] = "RdBu"

            answers["plot"]["features"]["faults"] = ask_and_validate(
                "\nWould you like to include faults on the map?\nType 'y' or 'yes' to plot, "
                "'n' or 'no' to leave off:", is_acceptable_value, allowed=["y", "yes", "n", "no"]
            )
            if answers["plot"]["features"]["faults"] == "y":
                answers["plot"]["features"]["faults"] = "yes"

            answers["plot"]["features"]["topography"] = ask_and_validate(
                "\nWould you like to include topography on the map?\nType 'y' or 'yes' to plot, "
                "'n' or 'no' to leave off:", is_acceptable_value, allowed=["y", "yes", "n", "no"]
            )
            if answers["plot"]["features"]["topography"] == "y":
                answers["plot"]["features"]["topography"] = "yes"

        # answers["data"]["save"] = ask_and_validate(
        #     "\nWould you like to save the extracted data for future use?\nType 'y' for yes:"
        # )

        # if str(answers["data"]["save"]).strip().lower() == "y" or \
        #    str(answers["data"]["save"]).strip().lower() == "yes":
        #     answers["data"]["save"] = ask_and_validate("What file name would you like to give\n"
        #                                                "the data?") + ".data"
    else:
        answers["plot"]["generate"] = "y"

        answers["plot"]["property"] = ask_and_validate("\nWhich property should be plotted?\n"
                                                       "Acceptable answers include Vp, Vs, "
                                                       "density, Qp, Qs, Vs30, or Elevation:",
                                                       is_acceptable_value,
                                                       allowed=["vp", "vs", "density",
                                                                "qp", "qs", "vs30", "elevation"], lower=True)
        answers["plot"]["title"] = "%s Slice From (%.2f, %.2f)" % (
            answers["cvm_list"],
            answers["bottom_left_point"]["x"],
            answers["bottom_left_point"]["y"]
        )
        answers["plot"]["features"]["scale"] = "discrete"
        answers["plot"]["features"]["colormap"] = "RdBu"
        answers["plot"]["features"]["faults"] = "Yes"
        answers["plot"]["features"]["topography"] = "No"

        file_str = hashlib.sha224(str(answers["plot"]["title"]).encode("UTF-8")).hexdigest()[0:5]
        answers["data"]["save"] = "extraction_" + file_str + ".data"

    answers["data"]["save"] = ask_and_validate(
        "\nWould you like the horizontal slice saved to disk? Type 'y' for yes, 'n' for no:",
        is_acceptable_value, "Enter y for yes, n for no.", allowed=["y", "n"]
    )

    if answers["data"]["save"].lower() == "y":
        answers["data"]["location"] = ask_and_validate(
            "Enter the folder location where this horizontal slice plot should be stored: "
        )
        answers["data"]["name"] = ask_and_validate(
            "Enter the file name (leave off the .png extension): "
        )

    save_config = ask_and_validate(
        "\nWould you like to save this configuration file for future use? Type 'y' for yes, 'n' for no:",
        is_acceptable_value, "Enter y for yes, n for no.", allowed=["y", "n"]
    )

    if save_config == "y":
        name = ask_and_validate(
            "Enter the file name to which this configuration should be saved (leave off the '.xml' ending): ",
        )

        if name.strip() == "":
            name = "plot_horizontal_slice.xml"

        with open(name.replace(".xml", "") + ".xml", "w") as fd:
            fd.write(xmltodict.unparse({"root": answers}, pretty=True))

    print(
        "\nThe configuration is now complete. The horizontal slice will generate now."
    )

    return answers


def main() -> int:
    """
    The main UCVM extract horizontal slice function.

    Returns:
        0 if successful. Raises an error code otherwise, if not.
    """
    try:
        options = UCVM.parse_options([
            {"short": "f", "long": "file", "value": True, "required": False},
            {"short": "a", "long": "advanced", "value": False, "required": False},
            {"short": "t", "long": "test", "value": False, "required": False}   # Hidden flag for testing.
        ], usage)
    except ValueError as v_err:
        print("[ERROR]: " + str(v_err) + "\n")
        sys.exit(-1)

    advanced = False
    if options["advanced"] is not None:
        advanced = True

    if options["file"] is not None:
        with open(options["file"], "r") as fd:
            slice_info = xmltodict.parse(fd.read())["root"]
    else:
        slice_info = ask_questions(advanced, options["test"] is not None)

    if options["test"] is None:
        HorizontalSlice.from_dictionary(slice_info).plot()
    else:
        hs = HorizontalSlice.from_dictionary(slice_info)
        hs.extract()
        for num in hs.extracted_data:
            print("%f " % num)

    return 0

if __name__ == "__main__":
    sys.exit(main())
