import os
import glob

from logging.config import dictConfig

from flask import send_from_directory
from flask_socketio import SocketIO

from pycs.database.Database import Database
from pycs.frontend.endpoints.ListJobs import ListJobs
from pycs.frontend.endpoints.ListLabelProviders import ListLabelProviders
from pycs.frontend.endpoints.ListModels import ListModels
from pycs.frontend.endpoints.ListProjects import ListProjects
from pycs.frontend.endpoints.additional.FolderInformation import FolderInformation
from pycs.frontend.endpoints.data.GetFile import GetFile
from pycs.frontend.endpoints.data.GetPreviousAndNextFile import GetPreviousAndNextFile
from pycs.frontend.endpoints.data.GetResizedFile import GetResizedFile
from pycs.frontend.endpoints.data.RemoveFile import RemoveFile
from pycs.frontend.endpoints.data.UploadFile import UploadFile
from pycs.frontend.endpoints.jobs.RemoveJob import RemoveJob
from pycs.frontend.endpoints.labels.CreateLabel import CreateLabel
from pycs.frontend.endpoints.labels.EditLabelName import EditLabelName
from pycs.frontend.endpoints.labels.EditLabelParent import EditLabelParent
from pycs.frontend.endpoints.labels.ListLabels import ListLabels
from pycs.frontend.endpoints.labels.RemoveLabel import RemoveLabel
from pycs.frontend.endpoints.pipelines.FitModel import FitModel
from pycs.frontend.endpoints.pipelines.PredictFile import PredictFile
from pycs.frontend.endpoints.pipelines.PredictModel import PredictModel
from pycs.frontend.endpoints.projects.CreateProject import CreateProject
from pycs.frontend.endpoints.projects.EditProjectDescription import EditProjectDescription
from pycs.frontend.endpoints.projects.EditProjectName import EditProjectName
from pycs.frontend.endpoints.projects.ExecuteExternalStorage import ExecuteExternalStorage
from pycs.frontend.endpoints.projects.ExecuteLabelProvider import ExecuteLabelProvider
from pycs.frontend.endpoints.projects.GetProjectModel import GetProjectModel
from pycs.frontend.endpoints.projects.ListCollections import ListCollections
from pycs.frontend.endpoints.projects.ListFiles import ListFiles
from pycs.frontend.endpoints.projects.RemoveProject import RemoveProject
from pycs.frontend.endpoints.results.ConfirmResult import ConfirmResult
from pycs.frontend.endpoints.results.CreateResult import CreateResult
from pycs.frontend.endpoints.results.EditResultData import EditResultData
from pycs.frontend.endpoints.results.EditResultLabel import EditResultLabel
from pycs.frontend.endpoints.results.GetProjectResults import GetProjectResults
from pycs.frontend.endpoints.results.GetResults import GetResults
from pycs.frontend.endpoints.results.RemoveResult import RemoveResult
from pycs.frontend.endpoints.results.ResetResults import ResetResults
from pycs.frontend.notifications.NotificationManager import NotificationManager
from pycs.frontend.util.JSONEncoder import JSONEncoder
from pycs.jobs.JobRunner import JobRunner
from pycs.util.PipelineCache import PipelineCache


class WebServer:
    """
    wrapper class for flask and socket.io which initializes most networking
    """

    # pylint: disable=line-too-long
    def __init__(self, app, settings: dict):

        dictConfig(settings["logging"])
        # initialize flask app instance
        self.app = app

        # initialize database
        db_file = settings["database"]
        self.logger.info(f'Loading database from \"{db_file}\"')
        self.db = Database(db_file)

        # start job runner
        self.logger.info('Starting job runner... ')
        self.jobs = JobRunner()

        # create pipeline cache
        self.logger.info('Creating pipeline cache')
        self.pipelines = PipelineCache(self.jobs)

        PRODUCTION = os.path.exists('webui/index.html')

        init_func = self.production_init if PRODUCTION else self.development_init
        kwargs, static_files = init_func()

        self.sio = SocketIO(self.app, **kwargs)#socketio.Server(**kwargs)
        # self.__app = socketio.WSGIApp(self.sio, self.app, static_files=static_files)
        self.host, self.port = settings['host'], settings['port']

        # set json encoder so database objects are serialized correctly
        self.app.json_encoder = JSONEncoder

        self.init_notifications()
        self.define_routes()
        self.logger.info("Server initialized")

    @property
    def logger(self):
        return self.app.logger

    def init_notifications(self):
        # create notification manager
        self.notifications = n = NotificationManager(self.sio)

        self.jobs.on_create(n.create_job)
        self.jobs.on_start(n.edit_job)
        self.jobs.on_progress(n.edit_job)
        self.jobs.on_finish(n.edit_job)
        self.jobs.on_remove(n.remove_job)

    def development_init(self):

        self.logger.info('Initializing development build')

        # set access control header to allow requests from Vue.js development server
        @self.app.after_request
        def after_request(response):
            # pylint: disable=unused-variable
            response.headers['Access-Control-Allow-Origin'] = '*'
            return response

        return dict(cors_allowed_origins='*', async_mode='eventlet'), None

    def production_init(self):

        self.logger.info('Initializing production build')

        kwargs = dict(async_mode='eventlet')
        if len(settings['allowedOrigins']) > 0:
            origins = settings['allowedOrigins']
            kwargs["cors_allowed_origins"] = origins

        # overwrite root path to serve index.html
        @self.app.route('/', methods=['GET'])
        def index():
            # pylint: disable=unused-variable
            return send_from_directory(os.path.join(os.getcwd(), 'webui'), 'index.html')

        return kwargs, self.static_files

    @property
    def static_files(self) -> dict:
        # find static files and folders
        static_files = {}

        for file_path in glob.glob('webui/*'):
            file_path = file_path.replace('\\', '/')
            static_files[file_path[5:]] = file_path

        # separately add svg files and set their correct mime type
        for svg_path in glob.glob('webui/img/*.svg'):
            svg_path = svg_path.replace('\\', '/')
            static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}

        return static_files

    def define_routes(self):
        # additional
        self.app.add_url_rule(
            '/folder',
            view_func=FolderInformation.as_view('folder_information')
        )

        # jobs
        self.app.add_url_rule(
            '/jobs',
            view_func=ListJobs.as_view('list_jobs', self.jobs)
        )
        self.app.add_url_rule(
            '/jobs/<identifier>/remove',
            view_func=RemoveJob.as_view('remove_job', self.jobs)
        )

        # models
        self.app.add_url_rule(
            '/models',
            view_func=ListModels.as_view('list_models', self.db)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/model',
            view_func=GetProjectModel.as_view('get_project_model', self.db)
        )

        # labels
        self.app.add_url_rule(
            '/label_providers',
            view_func=ListLabelProviders.as_view('label_providers', self.db)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/labels',
            view_func=ListLabels.as_view('list_labels', self.db)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/labels',
            view_func=CreateLabel.as_view('create_label', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/labels/<int:label_id>/remove',
            view_func=RemoveLabel.as_view('remove_label', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/labels/<int:label_id>/name',
            view_func=EditLabelName.as_view('edit_label_name', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/labels/<int:label_id>/parent',
            view_func=EditLabelParent.as_view('edit_label_parent', self.db, self.notifications)
        )

        # collections
        self.app.add_url_rule(
            '/projects/<int:project_id>/collections',
            view_func=ListCollections.as_view('list_collections', self.db)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/data/<int:collection_id>/<int:start>/<int:length>',
            view_func=ListFiles.as_view('list_collection_files', self.db)
        )

        # data
        self.app.add_url_rule(
            '/projects/<int:identifier>/data',
            view_func=UploadFile.as_view('upload_file', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/data/<int:start>/<int:length>',
            view_func=ListFiles.as_view('list_files', self.db)
        )
        self.app.add_url_rule(
            '/data/<int:identifier>/remove',
            view_func=RemoveFile.as_view('remove_file', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>',
            view_func=GetFile.as_view('get_file', self.db)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/<resolution>',
            view_func=GetResizedFile.as_view('get_resized_file', self.db)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/previous_next',
            view_func=GetPreviousAndNextFile.as_view('get_previous_and_next_file', self.db)
        )

        # results
        self.app.add_url_rule(
            '/projects/<int:project_id>/results',
            view_func=GetProjectResults.as_view('get_project_results', self.db)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/results',
            view_func=GetResults.as_view('get_results', self.db)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/results',
            view_func=CreateResult.as_view('create_result', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/reset',
            view_func=ResetResults.as_view('reset_results', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/results/<int:result_id>/remove',
            view_func=RemoveResult.as_view('remove_result', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/results/<int:result_id>/confirm',
            view_func=ConfirmResult.as_view('confirm_result', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/results/<int:result_id>/label',
            view_func=EditResultLabel.as_view('edit_result_label', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/results/<int:result_id>/data',
            view_func=EditResultData.as_view('edit_result_data', self.db, self.notifications)
        )

        # projects
        self.app.add_url_rule(
            '/projects',
            view_func=ListProjects.as_view('list_projects', self.db)
        )
        self.app.add_url_rule(
            '/projects',
            view_func=CreateProject.as_view('create_project', self.db, self.notifications, self.jobs)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/label_provider',
            view_func=ExecuteLabelProvider.as_view('execute_label_provider', self.db,
                                                   self.notifications, self.jobs)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/external_storage',
            view_func=ExecuteExternalStorage.as_view('execute_external_storage', self.db,
                                                     self.notifications, self.jobs)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/remove',
            view_func=RemoveProject.as_view('remove_project', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/name',
            view_func=EditProjectName.as_view('edit_project_name', self.db, self.notifications)
        )
        self.app.add_url_rule(
            '/projects/<int:identifier>/description',
            view_func=EditProjectDescription.as_view('edit_project_description', self.db,
                                                     self.notifications)
        )

        # pipelines
        self.app.add_url_rule(
            '/projects/<int:project_id>/pipelines/fit',
            view_func=FitModel.as_view('fit_model', self.db, self.jobs, self.pipelines)
        )
        self.app.add_url_rule(
            '/projects/<int:project_id>/pipelines/predict',
            view_func=PredictModel.as_view('predict_model', self.db, self.notifications,
                                           self.jobs, self.pipelines)
        )
        self.app.add_url_rule(
            '/data/<int:file_id>/predict',
            view_func=PredictFile.as_view('predict_file', self.db, self.notifications,
                                          self.jobs, self.pipelines)
        )

    def run(self):
        self.logger.info("Starting server...")
        return self.sio.run(self.app, host=self.host, port=self.port)
        # eventlet.wsgi.server(eventlet.listen((self.__host, self.__port)), self.__app)