# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.

"""
This module provides an interface to the MaterialsVirtualLab's MEGNet REST API
for rapid property prediction.
"""

import requests


class CrystalAIRester:
    """
    This is a high-level interface to the REST API for http://megnet.crystals.ai
    for property prediction. Using this API, you can use MatErials Graph
    Networks (MEGNet) developed by the Materials Virtual Lab to predict the
    properties of any given crystal. These models are trained on the latest
    versions of the Materials Project. The open-source code
    implementing these MEGNet models are available at
    https://github.com/materialsvirtuallab/megnet.

    For the details of MEGNet and benchmarks, please refer to the following work:
    Chen, C.; Ye, W.; Zuo, Y.; Zheng, C.; Ong, S. P. <i>Graph Networks as a Universal Machine Learning Framework
        for Molecules and Crystals.</i> Chemistry of Materials 2019, acs.chemmater.9b01294.
     DOI: <a href="https://doi.org/10.1021/acs.chemmater.9b01294">10.1021/acs.chemmater.9b01294</a>.</p>
    """

    def __init__(self):
        """
        Init for Rester.
        """
        self.session = requests.Session()
        self.url = "http://megnet.crystals.ai"

    def __enter__(self):
        """
        Support for "with" context.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Support for "with" context.
        """
        self.session.close()

    def get_available_models(self):
        """
        Returns:
            Available model names. It should be noted that model names starting
            with log10 are for the log10 of that quantity.
        """
        response = self.session.get(self.url + "/models")
        return response.json()

    def predict_mp(self, model_name, mp_id):
        """
        Predict using the http://megnet.crystals.ai API.

        :param model_name: An available model in the
            http://megnet.crystals.ai API. Use get_available_models to find
            the model names.
        :param mp_id: A Materials Project id.
        :return: Predicted value. It should be noted that model names starting
            with log10 are for the log10 of that quantity and you should apply
            10 ** prediction to get the actual value.
        """
        response = None
        url = self.url + f"/predict_mp/{model_name}/{mp_id}"
        try:
            response = self.session.get(url)
            if response.status_code in [200, 400]:
                return response.json()
            raise ValueError(f"REST query returned with error status code {response.status_code}")
        except Exception as ex:
            msg = f"{ex}. Content: {response.content}" if hasattr(response, "content") else str(ex)
            raise ValueError(msg)

    def predict_structure(self, model_name, structure):
        """
        Predict using the http://megnet.crystals.ai API.

        :param model_name: An available model in the
            http://megnet.crystals.ai API. Use get_available_models to find
            the model names.
        :param structure: A Pymatgen Structure.
        :return: Predicted value. It should be noted that model names starting
            with log10 are for the log10 of that quantity and you should apply
            10 ** prediction to get the actual value.
        """
        response = None
        url = self.url + f"/predict_structure/{model_name}"
        try:
            data = {"structure": structure.to(fmt="POSCAR"), "fmt": "POSCAR"}
            response = self.session.post(url, data=data)
            if response.status_code in [200, 400]:
                return response.json()
            raise ValueError(f"REST query returned with error status code {response.status_code}")
        except Exception as ex:
            msg = f"{ex}. Content: {response.content}" if hasattr(response, "content") else str(ex)
            raise ValueError(msg)
