import os
import argparse
import enum
import tarfile
import abc


class SupportedModels(enum.Enum):
    """
    Enumeration containing supported models
    """
    inception_v1 = 'inception_v1'
    inception_v2 = 'inception_v2'
    inception_v3 = 'inception_v3'
    inception_v4 = 'inception_v4'
    mobilenet_v1 = 'mobilenet_v1'
    mobilenet_v2 = 'mobilenet_v2'
    mobilenet_v3 = 'mobilenet_v3'
    resnet50_v1 = "resnet50_v1"
    resnet101_v1 = "resnet101_v1"
    resnet_v2_50 = "resnet_v2_50"
    resnet_v2_101 = "resnet_v2_101"
    resnet_v2_152 = "resnet_v2_152"


class Model(abc.ABC):
    """
    Base model class used to obtain the model (and perform any necessary operations to make it usable)
    """
    @property
    @abc.abstractmethod
    def model_url(self) -> str:
        """
        Returns the model download url
        Returns: url string

        """
        pass

    @property
    @abc.abstractmethod
    def package_name(self) -> str:
        """
        Returns the downloaded package path
        Returns: path string
        """
        pass

    def get_pretrained_model(self, destination):
        """
        Obtains a ready to use pretrained model file.
        Args:
            destination: path to where the file should be stored
        """
        print("Downloading the model from: {0}".format(self.model_url))
        os.system("curl -o {0} {1}".format(self.package_name, self.model_url))
        if tarfile.is_tarfile(self.package_name):
            with tarfile.open(self.package_name) as tar:
                if not os.path.exists(destination):
                    os.makedirs(destination)
                print("Extracting the model package to {0}".format(destination))
                tar.extractall(destination)


class InceptionV1(Model):
    """ Concrete implementation of the Model base class for Inception_v1"""
    # TODO This will download the ckpt file, need to add handling for
    #  https://github.com/tensorflow/models/tree/master/research/slim#exporting-the-inference-graph

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "http://download.tensorflow.org/models/inception_v1_2016_08_28.tar.gz"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "inception_v1.tar.gz"


class InceptionV2(Model):
    """ Concrete implementation of the Model base class for Inception_v2"""
    # TODO This will download the ckpt file, need to add handling for
    #  https://github.com/tensorflow/models/tree/master/research/slim#exporting-the-inference-graph

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "http://download.tensorflow.org/models/inception_v2_2016_08_28.tar.gz"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "inception_v2.tar.gz"


class InceptionV3(Model):
    """ Concrete implementation of the Model base class for Inception_v3"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/intel-optimized-tensorflow/models/v1_6/inceptionv3_fp32_pretrained_model.pb"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "inception_v3.pb"


class InceptionV4(Model):
    """ Concrete implementation of the Model base class for Inception_v4"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/intel-optimized-tensorflow/models/v1_6/inceptionv4_fp32_pretrained_model.pb"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "inception_v4.pb"


class Resnet50V1(Model):
    """ Concrete implementation of the Model base class for resnet50_V1"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/intel-optimized-tensorflow/models/v1_6/resnet50_fp32_pretrained_model.pb"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "resnet_v1_50.pb"


class Resnet101V1(Model):
    """ Concrete implementation of the Model base class for resnet101_V1"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/intel-optimized-tensorflow/models/v1_6/resnet101_fp32_pretrained_model.pb"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "resnet_v1_101.pb"


class ResnetV250(Model):
    """ Concrete implementation of the Model base class for Resnet v2 50 """
    def __init__(self):
        raise NotImplementedError("Resnet_V2_50 is not supported yet")

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        pass

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        pass


class ResnetV2101(Model):
    """ Concrete implementation of the Model base class for Resnet V2 101"""
    def __init__(self):
        raise NotImplementedError("Resnet_V2_101 is not supported yet")

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        pass

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        pass


class ResnetV2152(Model):
    """ Concrete implementation of the Model base class for Resnet v2 151"""
    def __init__(self):
        raise NotImplementedError("Resnet_V2_152 is not supported yet")

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        pass

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        pass


class MobilenetV1(Model):
    """ Concrete implementation of the Model base class for Mobilenetv1"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_1.0_224.tgz"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "mobilenet_v1_1.0_224.tgz"


class MobilenetV2(Model):
    """ Concrete implementation of the Model base class for Mobilenetv2"""
    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_224.tgz"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "mobilenet_v2_1.0_224.tgz"


class MobilenetV3(Model):
    """ Concrete implementation of the Model base class for Mobilenetv3"""

    @property
    def model_url(self) -> str:
        """
        Gets model download url
        Returns: model url

        """
        return "https://storage.googleapis.com/mobilenet_v3/checkpoints/v3-large_224_1.0_float.tgz"

    @property
    def package_name(self) -> str:
        """
        Gets the package name
        Returns: package name

        """
        return "v3-large_224_1.0_float.tgz"


def get_model(model: SupportedModels) -> Model:
    """
    Factory method that returns the requested model object
    Args:
        model: model from SupportedModels enumeration

    Returns: Concrete object inheriting the Model base class

    """
    model_map = {
        SupportedModels.inception_v1: InceptionV1(),
        SupportedModels.inception_v2: InceptionV2(),
        SupportedModels.inception_v3: InceptionV3(),
        SupportedModels.inception_v4: InceptionV4(),
        SupportedModels.mobilenet_v1: MobilenetV1(),
        SupportedModels.mobilenet_v2: MobilenetV2(),
        SupportedModels.mobilenet_v3: MobilenetV3(),
        SupportedModels.resnet50_v1: Resnet50V1(),
        SupportedModels.resnet101_v1: Resnet101V1()
    }
    return model_map.get(model)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Prepare pre-trained model for COCO object detection')
    parser.add_argument('--model_name', type=str, default='inception_v1',
                        help='model to download, default is inception_v1',
                        choices=["inception_v1", "inception_v2", "inception_v3", "inception_v4",
                                 "mobilenet_v1", "mobilenet_v2", "mobilenet_v3",
                                 "resnet50_v1", "resnet101_v1",
                                 "resnet_v2_50", "resnet_v2_101", "resnet_v2_152"])
    parser.add_argument('--model_path', type=str, default='{0}/model'.format(os.getcwd()),
                        help='directory to put models, default is {0}/model'.format(os.getcwd()))

    args = parser.parse_args()
    model_name = args.model_name
    model_path = args.model_path
    try:
        model = get_model(SupportedModels(model_name))
        model.get_pretrained_model(model_path)
    except AttributeError:
        print("The model {0} is not supported. Supported models: {1}"
              .format(model_name, SupportedModels.__members__.keys()))
