#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Interpreter version: brython (http://brython.info) (like python3)
#
# Imports =====================================================================
import json
import time
from os.path import join

from browser import alert  # TODO: Remove
from browser import document

from view import ViewController
from rules_view import RulesViewController

from components import OutputPicker
from components import make_request
from components import func_on_enter

# virtual filesystem / modules provided by REST API
from virtual_fs import settings


# Model =======================================================================
def is_issn(s):
    """
    Does the `s` looks like valid ISSN.

    Warning:
        This function doesn't check the ISSN validity, just the basic
        differentiation between URL and ISSN:
    """
    return all([
        c.isdigit() or c in "-x "
        for c in s.lower()
    ])


class AnalysisRunnerAdapter(object):
    """
    Adapter for the analyzers API.

    This adapter is mapped to the key / button press. It then launches querying
    of the REST API in periodic interval, updating the progress bar and so on.

    It is also responsible for remapping the REST names to locally used names
    and handling of error with connection.
    """
    @classmethod
    def on_complete(cls, req):
        # handle http errors
        if not (req.status == 200 or req.status == 0):
            ViewController.log_view.add(req.text)
            ViewController.urlbox_error.show(req.text)
            return

        resp = json.loads(req.text)

        # handle structured errors
        if not resp["status"]:
            ViewController.urlbox_error.show(resp["error"])
            ViewController.log_view.add(
                "Error while running analysis: %s" % resp["error"]
            )
            return

        # keep tracking of the progress
        if not resp["body"]["all_set"]:
            ViewController.url_progressbar.show(resp["body"]["progress"])
            time.sleep(settings.GUI_TO_REST_PERIODE)
            make_request(
                url=join(settings.API_PATH, "analyze"),
                data={'url': ViewController.url},
                on_complete=cls.on_complete,
            )
            return

        # finally save the data to inputs
        ViewController.url_progressbar.show(resp["body"]["progress"])
        ViewController.log_view.add(resp["log"])

        obtained_data = json.dumps(resp["body"]["values"])
        ViewController.log_view.add("Obtained data: " + str(obtained_data))

        cls.fill_inputs(resp["body"]["values"])

    @staticmethod
    def fill_inputs(values):
        """
        Callback called when the data is received. Basically translator from
        the REST names to locally used names.
        """
        name_map = {  # TODO: get rid of this crap
            "title_tags": "title",
            "subtitle_tags": "subtitle",
            "place_tags": "place",
            "lang_tags": "language",
            "keyword_tags": "keywords",
            "author_tags": "author",
            "publisher_tags": "publisher",
            "annotation_tags": "annotation",
            "creation_dates": "creation_date",
        }

        for remote_name in values.keys():
            # special adapter for aleph keyword view
            if remote_name == "keyword_tags":
                adder = ViewController.analysis_kw_handler.add_keyword
                for keyword in values[remote_name]:
                    adder(keyword["val"])
                continue

            local_name = name_map.get(remote_name, remote_name)
            setattr(ViewController, local_name, values[remote_name])

    @classmethod
    def start(cls, ev=None):
        """
        Start the analysis.
        """
        ViewController.log_view.add("Beginning AnalysisRunner request..")

        # reset all inputs
        ViewController.reset_bars()

        # read the urlbox
        url = ViewController.url.strip()

        # make sure, that `url` was filled
        if not url:
            ViewController.urlbox_error.show("URL musí být vyplněna.")
            return

        if is_issn(url):
            ViewController.url_progressbar.hide()
            ViewController.url = ""
            ViewController.issn = url
            AlephISSNReaderAdapter.start()
            return

        ViewController.urlbox_error.hide()

        # normalize the `url`
        if not (url.startswith("http://") or url.startswith("https://")):
            url = "http://" + url
            ViewController.url = url  # store normalized url back to input

        make_request(
            url=join(settings.API_PATH, "analyze"),
            data={'url': url},
            on_complete=cls.on_complete
        )


class AlephISSNReaderAdapter(object):
    """
    Adapter for the Aleph API.

    This adapter is mapped to the key / button press. It then send the query
    to REST API and obtain data.

    It is also responsible for remapping the REST names to locally used names
    and handling of error with connection.
    """
    @classmethod
    def on_complete(cls, req):
        # handle http errors
        if not (req.status == 200 or req.status == 0):
            ViewController.log_view.add(req.text)
            ViewController.issnbox_error.show(req.text)
            ViewController.issn_progressbar.show(100)
            return

        try:
            resp = json.loads(req.text)
        except ValueError:
            resp = None

        if not resp:
            ViewController.issnbox_error.show(
                "Pro zadané ISSN nebyly nalezeny žádná data."
            )
            ViewController.log_view.add(
                "Error while reading fdata from Aleph: %s" % str(resp.text)
            )
            ViewController.issn_progressbar.show(100)
            return

        ViewController.issn_progressbar.reset()

        dataset = resp[0]
        cls._handle_aleph_keyword_view(dataset)
        AnalysisRunnerAdapter.fill_inputs(dataset)

    @staticmethod
    def _handle_aleph_keyword_view(dataset):
        """
        Translate the Aleph keywords to locally used data.
        """
        # redirect the keywords to Aleph view
        adder = ViewController.aleph_kw_handler.add_keyword
        for keyword in dataset.get("keyword_tags", []):
            adder(keyword["val"])

        if "keyword_tags" in dataset:
            del dataset["keyword_tags"]

    @classmethod
    def start(cls, ev=None):
        """
        Start the query to aleph by ISSN.
        """
        ViewController.log_view.add("Beginning AlephReader request..")

        ViewController.issnbox_error.reset()
        issn = ViewController.issn.strip()

        # make sure, that `issn` was filled
        if not issn:
            ViewController.issnbox_error.show("ISSN nebylo vyplněno!")
            ViewController.log_view.add("No ISSN! Aborting.")
            return

        ViewController.issnbox_error.hide()
        ViewController.issn_progressbar.reset()
        ViewController.issn_progressbar.show(50)
        ViewController.log_view.add("For ISSN `%s`." % issn)

        make_request(
            url=join(settings.API_PATH, "aleph/records_by_issn"),
            data={'issn': issn},
            on_complete=cls.on_complete
        )


class MARCGeneratorAdapter(object):
    """
    Adapter for the REST API for generation of the MRC / MARC / DC exports.

    This adapter is mapped to the key / button press. It then send the query
    to REST API and obtain data.

    It is also responsible for remapping the REST names to locally used names
    and handling of error with connection.
    """
    @classmethod
    def on_complete(cls, req):
        """
        Callback called when the request to REST is done. Handles the errors
        and if there is none, :class:`.OutputPicker` is shown.
        """
        # handle http errors
        if not (req.status == 200 or req.status == 0):
            ViewController.log_view.add(req.text)
            alert(req.text)  # TODO: better handling
            return

        try:
            resp = json.loads(req.text)
        except ValueError:
            resp = None

        if not resp:
            alert("Chyba při konverzi!")  # TODO: better
            ViewController.log_view.add(
                "Error while generating MARC: %s" % resp.text
            )
            return

        OutputPicker.show(resp)

    @classmethod
    def start(cls, ev):
        """
        Read all data from Views and send them to the backend.
        """
        ev.preventDefault()
        ev.stopPropagation()

        ViewController.log_view.add("Beginning MARCGenerator request..")

        if not ViewController.validate():
            ViewController.urlbox_error.show(
                "Vyplňte prosím zvýrazněné hodnoty."
            )
            ViewController.log_view.add("Aborting. Not all required data set.")
            return

        # make sure, that `url` was filled
        url = ViewController.url.strip()
        if not (url.startswith("http://") or url.startswith("https://")):
            ViewController.urlbox_error.show(
                "`%s` není validní URL! Zkuste přidat http:// | https://" % url
            )
            return

        properties = ViewController.get_all_properties()
        properties["rules"] = RulesViewController.get_dict()

        # keep the url_id from hidden input
        if "url_id" in document and document["url_id"].value:
            properties["url_id"] = document["url_id"].value

        ViewController.log_view.add(
            "Sending following data: %s" % repr(properties)
        )

        make_request(
            url=join(settings.API_PATH, "to_output"),
            data={"data": json.dumps(properties)},
            on_complete=cls.on_complete
        )


class SeederAdapter(object):
    """
    Read the data sent by Seeder. Data are included in the `seeder_data`
    element and packed in JSON.
    """
    @classmethod
    def read_data(cls):
        if not document["seeder_data"].is_set:
            return

        dataset = json.loads(document["seeder_data"].text)
        ViewController.log_view.add(
            "Obtained data from Seeder: %s" % str(dataset)
        )

        AnalysisRunnerAdapter.fill_inputs(dataset)

        if "rules" in dataset:
            RulesViewController.set_dict(dataset["rules"])


def reset_inputs(ev):
    """
    Bund the reset button to action.
    """
    ViewController.log_view.add(".reset() called.")
    ViewController.reset()


# bind buttons to actions
document["run_button"].bind("click", AnalysisRunnerAdapter.start)
document["reset_button"].bind("click", reset_inputs)
document["url"].bind("keypress", func_on_enter(AnalysisRunnerAdapter.start))
document["issn_run_button"].bind("click", AlephISSNReaderAdapter.start)
document["issn"].bind("keypress", func_on_enter(AlephISSNReaderAdapter.start))
document["marc_button"].bind("click", MARCGeneratorAdapter.start)
document["whole_brython_progress_bar"].style.display = "none"
document["content"].style.display = "block"

# read the data from the Seeder
SeederAdapter.read_data()

ViewController.log_view.add("Frontend fully loaded.")
