import os
import sys
import tempfile
import importlib
import inspect
import logging
import logging.handlers
import bottle
from bottle import run, route, request, HTTPResponse, Bottle

from tomviz_acquisition import jsonrpc
from tomviz_acquisition.utility import inject
from tomviz_acquisition.acquisition import AbstractSource
import shutil

# For python 3
try:
    from imp import reload
except ImportError:
    pass


ADAPTER = 'tests.mock.source.ApiAdapter'
HOST = 'localhost'
PORT = 8080
LOG_BUF_SIZE = 65536

logger = logging.getLogger('tomviz_acquisition')
app = Bottle()


def _load_source_adapter(source_adapter):
    logger.info('Loading source_adapter: %s', source_adapter)
    # First load the chosen source_adapter
    module, cls = source_adapter.rsplit('.', 1)
    try:
        imported = importlib.import_module(module)
    except ImportError:
        logger.error('Unable to load module: %s', module)
        raise

    try:
        cls = getattr(imported, cls)
    except AttributeError:
        logger.error('Unable to get constructor for: %s', source_adapter)
        raise

    return cls


# TODO Refactor, flake8 is complaining about the complexity of this function.
# This endpoints currently have to be defined in this way to allow the injection
# of the source adapter we need to revisit this. For now I added noqa
def _setup_adapter(source_adapter): # noqa
    """
    Setup up the JSON-RPC endpoints for a give source adapter
    """
    cls = _load_source_adapter(source_adapter)

    # Ensure that the adapter implements the interface
    bases = inspect.getmro(cls)
    if len(bases) < 2 or bases[1] != AbstractSource:
        logger.error('Adapter %s, does not derive from %s', source_adapter,
                     '.'.join([AbstractSource.__module__,
                               AbstractSource.__name__]))

    source_adapter = cls()
    slices = {}

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def describe(source_adapter, method=None):

        if method is None:
            return {
                'name': '%s.%s' % (inspect.getmodule(source_adapter).__name__,
                                   type(source_adapter).__name__)
            }

        if not hasattr(source_adapter, method):
            raise Exception('Method %s not found.' % method)

        func = getattr(source_adapter, method)

        description = []
        if hasattr(func, 'description'):
            description = getattr(func, 'description')

        return description

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def connect(source_adapter, **params):
        return source_adapter.connect(**params)

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def disconnect(source_adapter, **params):
        return source_adapter.disconnect(**params)

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def tilt_params(source_adapter, **params):
        return source_adapter.tilt_params(**params)

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def acquisition_params(source_adapter, **params):
        return source_adapter.acquisition_params(**params)

    def _base_url():
        return '%s://%s' % (bottle.request.urlparts.scheme,
                            bottle.request.urlparts.netloc)

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def preview_scan(source_adapter):
        id = 'preview_scan_slice'
        slices[id] = source_adapter.preview_scan()
        return '%s/data/%s' % (_base_url(), id)

    @jsonrpc.endpoint(path='/acquisition')
    @inject(source_adapter)
    def stem_acquire(source_adapter):
        id = 'stem_acquire_slice'
        data = source_adapter.stem_acquire()

        if data is None:
            return None

        metadata = None
        # Do we have any meta data
        if isinstance(data, tuple):
            (metadata, data) = data
        slices[id] = data

        image_data_url = '%s/data/%s' % (_base_url(), id)

        if metadata is not None:
            return {
                'meta': metadata,
                'imageUrl': image_data_url
            }
        else:
            return image_data_url

    @route('/data/<id>')
    @inject(source_adapter)
    def data(source_adapter, id):
        bottle.response.headers['Content-Type'] = 'image/tiff'
        if hasattr(source_adapter, 'image_data_mimetype'):
            bottle.response.headers['Content-Type'] \
                = source_adapter.image_data_mimetype

        if id not in slices:
            raise HTTPResponse(body='Acquisition data not found.', status=404)

        return slices[id]


def _log(log):
    bottle.response.headers['Content-Type'] = 'text/plain'
    bytes = request.query.bytes

    if log not in tomviz.LOG_PATHS:
        raise HTTPResponse(body='Invalid log parameter: %s.' % log,
                           status=400)
    path = tomviz.LOG_PATHS[log]

    if not os.path.exists(path):
        raise HTTPResponse(body='Log file does not exist.',
                           status=400)

    file_size = os.path.getsize(path)
    length = int(bytes) or file_size
    file_size1 = 0
    if length > file_size:
        path1 = path + '.1'
        if os.path.exists(path1):
            file_size1 = os.path.getsize(path1)

    def stream():
        read_length = length
        if read_length > file_size and file_size1:
            read_length = length - file_size
            with open(path1, 'rb') as f:
                if read_length < file_size1:
                    f.seek(-read_length, os.SEEK_END)
                while True:
                    data = f.read(LOG_BUF_SIZE)
                    if not data:
                        break
                    yield data
            read_length = file_size
        with open(path, 'rb') as f:
            if read_length < file_size:
                f.seek(-read_length, os.SEEK_END)
            while True:
                data = f.read(LOG_BUF_SIZE)
                if not data:
                    break
                yield data

    return stream()


def _deploy_module(name, source):
    try:
        tmp_dir = tempfile.mkdtemp()

        sys.path.append(tmp_dir)

        module_path = '%s.py' % os.path.join(tmp_dir, name)
        with open(module_path, 'w') as fp:
            fp.write(source)

        if name in sys.modules:
            reload(sys.modules[name])
        else:
            importlib.import_module(name)

    finally:
        sys.path.remove(tmp_dir)
        shutil.rmtree(tmp_dir)


def deploy_adapter(module, name, source):
    _deploy_module(module, source)
    adapter_name = '%s.%s' % (module, name)
    _setup_adapter(adapter_name)


def setup(adapter=None, dev=False):
    if adapter is None:
        adapter = ADAPTER

    if dev:
        jsonrpc.endpoint(path='/dev')(deploy_adapter)
    else:
        _setup_adapter(adapter)

    route('/log/<log>')(_log)


def start(host=HOST, port=PORT, debug=True, dev=False, adapter=ADAPTER):
    with app:
        setup(adapter, dev)
        logger.info('Starting HTTP server')
        run(host=host, port=port, debug=debug)
