# -*- coding: utf-8 -*-

import math
import mock
import os
import pytest
import requests
import shutil
import six
import struct
import time

from girder.constants import SortDir
from girder.models.file import File
from girder.models.item import Item
from girder.models.setting import Setting
from girder.models.token import Token
from girder.models.user import User

from girder_jobs.models.job import Job

from girder_large_image.models.image_item import ImageItem

from large_image import getTileSource
from girder_large_image import constants
from girder_large_image import getGirderTileSource
from girder_large_image import loadmodelcache

from . import girder_utilities as utilities
from .girder_utilities import girderWorker, unbindLargeImage  # noqa


def _testTilesZXY(server, admin, itemId, metadata, tileParams=None,
                  imgHeader=utilities.JPEGHeader, token=None):
    """
    Test that the tile server is serving images.

    :param itemId: the item ID to get tiles from.
    :param metadata: tile information used to determine the expected
                     valid queries.  If 'sparse' is added to it, tiles
                     are allowed to not exist above that level.
    :param tileParams: optional parameters to send to the tile query.
    :param imgHeader: if something other than a JPEG is expected, this is
                      the first few bytes of the expected image.
    """
    if tileParams is None:
        tileParams = {}
    if token:
        kwargs = {'token': token}
    else:
        kwargs = {'user': admin}
    # We should get images for all valid levels, but only within the
    # expected range of tiles.
    for z in range(metadata.get('minLevel', 0), metadata['levels']):
        maxX = math.ceil(float(metadata['sizeX']) * 2 ** (
            z - metadata['levels'] + 1) / metadata['tileWidth']) - 1
        maxY = math.ceil(float(metadata['sizeY']) * 2 ** (
            z - metadata['levels'] + 1) / metadata['tileHeight']) - 1
        # Check the four corners on each level
        for (x, y) in ((0, 0), (maxX, 0), (0, maxY), (maxX, maxY)):
            resp = server.request(path='/item/%s/tiles/zxy/%d/%d/%d' % (
                itemId, z, x, y), params=tileParams, isJson=False,
                **kwargs)
            if (resp.output_status[:3] != b'200' and
                    metadata.get('sparse') and z > metadata['sparse']):
                assert utilities.respStatus(resp) == 404
                continue
            assert utilities.respStatus(resp) == 200
            image = utilities.getBody(resp, text=False)
            assert image[:len(imgHeader)] == imgHeader
        # Check out of range each level
        for (x, y) in ((-1, 0), (maxX + 1, 0), (0, -1), (0, maxY + 1)):
            resp = server.request(path='/item/%s/tiles/zxy/%d/%d/%d' % (
                itemId, z, x, y), params=tileParams, **kwargs)
            if x < 0 or y < 0:
                assert utilities.respStatus(resp) == 400
                assert 'must be positive integers' in resp.json['message']
            else:
                assert utilities.respStatus(resp) == 404
                assert ('does not exist' in resp.json['message'] or
                        'outside layer' in resp.json['message'])
    # Check negative z level
    resp = server.request(path='/item/%s/tiles/zxy/-1/0/0' % itemId,
                          params=tileParams, **kwargs)
    assert utilities.respStatus(resp) == 400
    assert 'must be positive integers' in resp.json['message']
    # Check non-integer z level
    resp = server.request(path='/item/%s/tiles/zxy/abc/0/0' % itemId,
                          params=tileParams, **kwargs)
    assert utilities.respStatus(resp) == 400
    assert 'must be integers' in resp.json['message']
    # If we set the minLevel, test one lower than it
    if 'minLevel' in metadata:
        resp = server.request(path='/item/%s/tiles/zxy/%d/0/0' % (
            itemId, metadata['minLevel'] - 1), params=tileParams, **kwargs)
        assert utilities.respStatus(resp) == 404
        assert 'layer does not exist' in resp.json['message']
    # Check too large z level
    resp = server.request(path='/item/%s/tiles/zxy/%d/0/0' % (
        itemId, metadata['levels']), params=tileParams, **kwargs)
    assert utilities.respStatus(resp) == 404
    assert 'layer does not exist' in resp.json['message']


def _createTestTiles(server, admin, params=None, info=None, error=None):
    """
    Discard any existing tile set on an item, then create a test tile set
    with some optional parameters.

    :param params: optional parameters to use for the tiles.
    :param info: if present, the tile information must match all values in
                 this dictionary.
    :param error: if present, expect to get an error from the tile info
                  query and ensure that this string is in the error
                  message.
    :returns: the tile information dictionary.
    """
    if params is None:
        params = {}
    try:
        resp = server.request(path='/item/test/tiles', user=admin,
                              params=params)
        if error:
            assert utilities.respStatus(resp) == 400
            assert error in resp.json['message']
            return None
    except AssertionError as exc:
        if error:
            assert error in exc.args[0]
            return
        else:
            raise
    assert utilities.respStatus(resp) == 200
    infoDict = resp.json
    if info:
        for key in info:
            assert infoDict[key] == info[key]
    return infoDict


def _postTileViaHttp(server, admin, itemId, fileId, jobAction=None):
    """
    When we know we need to process a job, we have to use an actual http
    request rather than the normal simulated request to cherrypy.  This is
    required because cherrypy needs to know how it was reached so that
    girder_worker can reach it when done.

    :param itemId: the id of the item with the file to process.
    :param fileId: the id of the file that should be processed.
    :param jobAction: if 'delete', delete the job immediately.
    :returns: metadata from the tile if the conversion was successful,
              False if it converted but didn't result in useable tiles, and
              None if it failed.
    """
    headers = {
        'Accept': 'application/json',
        'Girder-Token': str(Token().createToken(admin)['_id'])
    }
    req = requests.post('http://127.0.0.1:%d/api/v1/item/%s/tiles' % (
        server.boundPort, itemId), headers=headers,
        data={'fileId': fileId})
    assert req.status_code == 200
    # If we ask to create the item again right away, we should be told that
    # either there is already a job running or the item has already been
    # added
    req = requests.post('http://127.0.0.1:%d/api/v1/item/%s/tiles' % (
        server.boundPort, itemId), headers=headers,
        data={'fileId': fileId})
    assert req.status_code == 400
    assert ('Item already has' in req.json()['message'] or
            'Item is scheduled' in req.json()['message'])

    if jobAction == 'delete':
        Job().remove(Job().find({}, sort=[('_id', SortDir.DESCENDING)])[0])

    starttime = time.time()
    resp = None
    while time.time() - starttime < 30:
        try:
            resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
            assert utilities.respStatus(resp) == 200
            break
        except AssertionError:
            result = resp.json['message']
            if "didn't meet requirements" in result:
                return False
            if 'No large image file' in result:
                return None
            assert 'is still pending creation' in result
        time.sleep(0.1)
    assert utilities.respStatus(resp) == 200
    return resp.json


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesFromPTIF(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    # We should already have tile information.  Ask to delete it so we can
    # do other tests
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True
    # Now we shouldn't have tile information
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId, user=admin)
    assert utilities.respStatus(resp) == 404
    assert 'No large image file' in resp.json['message']
    # Asking to delete the tile information succeeds but does nothing
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is False
    # Ask to make this a tile-based item with an invalid file ID
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin, params={'fileId': itemId})
    assert utilities.respStatus(resp) == 400
    assert 'No such file' in resp.json['message']

    # Ask to make this a tile-based item properly
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin, params={'fileId': fileId})
    assert utilities.respStatus(resp) == 200
    # Now the tile request should tell us about the file.  These are
    # specific to our test file
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 58368
    assert tileMetadata['sizeY'] == 12288
    assert tileMetadata['levels'] == 9
    assert tileMetadata['magnification'] == 40
    assert tileMetadata['mm_x'] == 0.00025
    assert tileMetadata['mm_y'] == 0.00025
    tileMetadata['sparse'] = 5
    _testTilesZXY(server, admin, itemId, tileMetadata)

    # Check that we conditionally get JFIF headers
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JFIFHeader)] != utilities.JFIFHeader

    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False,
                          params={'encoding': 'JFIF'})
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JFIFHeader)] == utilities.JFIFHeader

    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False,
                          additionalHeaders=[('User-Agent', 'iPad')])
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JFIFHeader)] == utilities.JFIFHeader

    resp = server.request(
        path='/item/%s/tiles/zxy/0/0/0' % itemId, user=admin,
        isJson=False, additionalHeaders=[(
            'User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X '
            '10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) '
            'Version/10.0.3 Safari/602.4.8')])
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JFIFHeader)] == utilities.JFIFHeader

    resp = server.request(
        path='/item/%s/tiles/zxy/0/0/0' % itemId, user=admin,
        isJson=False, additionalHeaders=[(
            'User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
            'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 '
            'Safari/537.36')])
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JFIFHeader)] != utilities.JFIFHeader

    # Ask to make this a tile-based item again
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin, params={'fileId': fileId})
    assert utilities.respStatus(resp) == 400
    assert 'Item already has' in resp.json['message']

    # We should be able to delete the large image information
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True

    # We should no longer have tile information
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']

    # We should be able to re-add it (we are also testing that fileId is
    # optional if there is only one file).
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesFromTest(server, admin, fsAssetstore):
    publicFolder = utilities.namedFolder(admin, 'Public')
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    items = [{'itemId': str(file['itemId']), 'fileId': str(file['_id'])}]
    # We should already have tile information.  Ask to delete it so we can
    # do other tests
    resp = server.request(path='/item/%s/tiles' % str(file['itemId']),
                          method='DELETE', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True
    # Create a second item
    resp = server.request(path='/item', method='POST', user=admin,
                          params={'folderId': publicFolder['_id'],
                                  'name': 'test'})
    assert utilities.respStatus(resp) == 200
    itemId = str(resp.json['_id'])
    items.append({'itemId': itemId})
    # Check that we can't create a tile set with another item's file
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin,
                          params={'fileId': items[0]['fileId']})
    assert utilities.respStatus(resp) == 400
    assert 'The provided file must be in the provided item' in resp.json['message']
    # Now create a test tile with the default options
    params = {'encoding': 'JPEG'}
    meta = _createTestTiles(server, admin, params, {
        'tileWidth': 256, 'tileHeight': 256,
        'sizeX': 256 * 2 ** 9, 'sizeY': 256 * 2 ** 9, 'levels': 10
    })
    _testTilesZXY(server, admin, 'test', meta, params)
    # Test most of our parameters in a single special case
    params = {
        'minLevel': 2,
        'maxLevel': 5,
        'tileWidth': 160,
        'tileHeight': 120,
        'sizeX': 5000,
        'sizeY': 3000,
        'encoding': 'JPEG'
    }
    meta = _createTestTiles(server, admin, params, {
        'tileWidth': 160, 'tileHeight': 120,
        'sizeX': 5000, 'sizeY': 3000, 'levels': 6
    })
    meta['minLevel'] = 2
    _testTilesZXY(server, admin, 'test', meta, params)
    # Test the fractal tiles with PNG
    params = {'fractal': 'true'}
    meta = _createTestTiles(server, admin, params, {
        'tileWidth': 256, 'tileHeight': 256,
        'sizeX': 256 * 2 ** 9, 'sizeY': 256 * 2 ** 9, 'levels': 10
    })
    _testTilesZXY(server, admin, 'test', meta, params, utilities.PNGHeader)
    # Test that the fractal isn't the same as the non-fractal
    resp = server.request(path='/item/test/tiles/zxy/0/0/0', user=admin,
                          params=params, isJson=False)
    image = utilities.getBody(resp, text=False)
    resp = server.request(path='/item/test/tiles/zxy/0/0/0', user=admin,
                          isJson=False)
    assert utilities.getBody(resp, text=False) != image
    # Test each property with an invalid value
    badParams = {
        'minLevel': 'a',
        'maxLevel': False,
        'tileWidth': (),
        'tileHeight': [],
        'sizeX': {},
        'sizeY': 1.3,
        'encoding': 2,
    }
    for key in badParams:
        err = ('parameter is an incorrect' if key != 'encoding' else
               'Invalid encoding')
        _createTestTiles(server, admin, {key: badParams[key]}, error=err)


@pytest.mark.usefixtures('unbindLargeImage')  # noqa
@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesFromPNG(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    server = boundServer
    file = utilities.uploadTestFile('yb10kx5k.png', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    tileMetadata = _postTileViaHttp(server, admin, itemId, fileId)
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 10000
    assert tileMetadata['sizeY'] == 5000
    assert tileMetadata['levels'] == 7
    assert tileMetadata['magnification'] is None
    assert tileMetadata['mm_x'] is None
    assert tileMetadata['mm_y'] is None
    _testTilesZXY(server, admin, itemId, tileMetadata)
    # Ask to make this a tile-based item with an missing file ID (there are
    # now two files, so this will now fail).
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'Missing "fileId"' in resp.json['message']
    # We should be able to delete the tiles
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True
    # We should no longer have tile informaton
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']
    # This should work with a PNG with transparency, too.
    file = utilities.uploadTestFile('yb10kx5ktrans.png', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    tileMetadata = _postTileViaHttp(server, admin, itemId, fileId)
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 10000
    assert tileMetadata['sizeY'] == 5000
    assert tileMetadata['levels'] == 7
    _testTilesZXY(server, admin, itemId, tileMetadata)
    # We should be able to delete the tiles
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True
    # We should no longer have tile information
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']
    # Make sure we don't auto-create a largeImage
    file = utilities.uploadTestFile(
        'yb10kx5k.png', admin, fsAssetstore, name='yb10kx5k.tiff')
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']

    # Try to create an image, but delete the job and check that it fails.
    fileId = str(file['_id'])
    result = _postTileViaHttp(server, admin, itemId, fileId, jobAction='delete')
    assert result is None
    # If we end the test here, girder_worker may upload a file that gets
    # discarded, but do so in a manner that interfers with cleaning up the test
    # temp directory.  By running another task, this is less likely to occur.

    # Creating it again should work
    tileMetadata = _postTileViaHttp(server, admin, itemId, fileId)
    assert tileMetadata['levels'] == 7


@pytest.mark.usefixtures('unbindLargeImage')  # noqa
@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesFromGreyscale(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    file = utilities.uploadTestFile('grey10kx5k.tif', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    tileMetadata = _postTileViaHttp(boundServer, admin, itemId, fileId)
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 10000
    assert tileMetadata['sizeY'] == 5000
    assert tileMetadata['levels'] == 7
    assert tileMetadata['magnification'] is None
    assert tileMetadata['mm_x'] is None
    assert tileMetadata['mm_y'] is None
    _testTilesZXY(boundServer, admin, itemId, tileMetadata)


@pytest.mark.usefixtures('unbindLargeImage')  # noqa
@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesFromUnicodeName(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    # Unicode file names shouldn't cause problems when generating tiles.
    file = utilities.uploadTestFile('yb10kx5k.png', admin, fsAssetstore)
    # Our normal testing method doesn't pass through the unicode name
    # properly, so just change it after upload.
    file = File().load(file['_id'], force=True)
    file['name'] = u'\u0441\u043b\u0430\u0439\u0434'
    file = File().save(file)
    fileId = str(file['_id'])

    itemId = str(file['itemId'])
    item = Item().load(itemId, force=True)
    item['name'] = u'item \u0441\u043b\u0430\u0439\u0434'
    item = Item().save(item)

    tileMetadata = _postTileViaHttp(boundServer, admin, itemId, fileId)
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 10000
    assert tileMetadata['sizeY'] == 5000
    assert tileMetadata['levels'] == 7
    assert tileMetadata['magnification'] is None
    assert tileMetadata['mm_x'] is None
    assert tileMetadata['mm_y'] is None
    _testTilesZXY(boundServer, admin, itemId, tileMetadata)


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesWithUnicodeName(server, admin, fsAssetstore):
    # Unicode file names shouldn't cause problems when accessing ptifs.
    # This requires an appropriate version of the python libtiff module.
    name = u'\u0441\u043b\u0430\u0439\u0434.ptif'
    origpath = utilities.externaldata('data/sample_image.ptif.sha512')
    altpath = os.path.join(os.path.dirname(origpath), name)
    if os.path.exists(altpath):
        os.unlink(altpath)
    shutil.copy(origpath, altpath)
    file = utilities.uploadFile(altpath, admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 58368
    assert tileMetadata['sizeY'] == 12288


@pytest.mark.usefixtures('unbindLargeImage')  # noqa
@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesFromBadFiles(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    # As of vips 8.2.4, alpha and unusual channels are removed upon
    # conversion to a JPEG-compressed tif file.  Originally, we performed a
    # test to show that these files didn't work.  They now do (though if
    # the file has a separated color space, it may not work as expected).

    # Uploading a non-image file should run a job, but not result in tiles
    file = utilities.uploadTestFile('notanimage.txt', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    tileMetadata = _postTileViaHttp(boundServer, admin, itemId, fileId)
    assert tileMetadata is None
    resp = boundServer.request(path='/item/%s/tiles' % itemId,
                               method='DELETE', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is False


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testThumbnails(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    # We should already have tile information.  Ask to delete it so we can
    # do other tests
    resp = server.request(path='/item/%s/tiles' % itemId, method='DELETE',
                          user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json['deleted'] is True
    # We shouldn't be able to get a thumbnail yet
    resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                          user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']
    # Ask to make this a tile-based item
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST',
                          user=admin, params={'fileId': fileId})
    assert utilities.respStatus(resp) == 200
    # Get metadata to use in our thumbnail tests
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json
    # Now we should be able to get a thumbnail
    resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JPEGHeader)] == utilities.JPEGHeader
    defaultLength = len(image)

    # Test width and height using PNGs
    resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                          user=admin, isJson=False,
                          params={'encoding': 'PNG'})
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert max(width, height) == 256
    # We know that we are using an example where the width is greater than
    # the height
    origWidth = int(tileMetadata['sizeX'] *
                    2 ** -(tileMetadata['levels'] - 1))
    origHeight = int(tileMetadata['sizeY'] *
                     2 ** -(tileMetadata['levels'] - 1))
    assert height == int(width * origHeight / origWidth)
    resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                          user=admin, isJson=False,
                          params={'encoding': 'PNG', 'width': 200})
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 200
    assert height == int(width * origHeight / origWidth)

    # Test bad parameters
    badParams = [
        ({'encoding': 'invalid'}, 400, 'Invalid encoding'),
        ({'width': 'invalid'}, 400, 'incorrect type'),
        ({'width': 0}, 400, 'Invalid width or height'),
        ({'width': -5}, 400, 'Invalid width or height'),
        ({'height': 'invalid'}, 400, 'incorrect type'),
        ({'height': 0}, 400, 'Invalid width or height'),
        ({'height': -5}, 400, 'Invalid width or height'),
        ({'jpegQuality': 'invalid'}, 400, 'incorrect type'),
        ({'jpegSubsampling': 'invalid'}, 400, 'incorrect type'),
        ({'fill': 'not a color'}, 400, 'unknown color'),
    ]
    for entry in badParams:
        resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                              user=admin,
                              params=entry[0])
        assert utilities.respStatus(resp) == entry[1]
        assert entry[2] in resp.json['message']

    # Test that we get a thumbnail from a cached file
    resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JPEGHeader)] == utilities.JPEGHeader
    assert len(image) == defaultLength

    # We should report some thumbnails
    item = Item().load(itemId, user=admin)
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present > 2

    # Remove the item, and then there should be zero files.
    Item().remove(item)
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 0


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testContentDisposition(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])

    params = {'encoding': 'PNG', 'width': 200}
    path = '/item/%s/tiles/thumbnail' % itemId
    params['contentDisposition'] = 'inline'
    resp = server.request(path=path, user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    assert resp.headers['Content-Disposition'].startswith('inline')
    assert (
        resp.headers['Content-Disposition'].endswith('.png') or
        'largeImageThumbnail' in resp.headers['Content-Disposition'])
    params['contentDisposition'] = 'attachment'
    resp = server.request(path=path, user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    assert resp.headers['Content-Disposition'].startswith('attachment')
    assert (
        resp.headers['Content-Disposition'].endswith('.png') or
        'largeImageThumbnail' in resp.headers['Content-Disposition'])
    params['contentDisposition'] = 'other'
    resp = server.request(path=path, user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    assert (
        resp.headers.get('Content-Disposition') is None or
        'largeImageThumbnail' in resp.headers['Content-Disposition'])
    del params['contentDisposition']
    resp = server.request(path=path, user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    assert (
        resp.headers.get('Content-Disposition') is None or
        'largeImageThumbnail' in resp.headers['Content-Disposition'])


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testRegions(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    # Get metadata to use in our tests
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json

    # Test bad parameters
    badParams = [
        ({'encoding': 'invalid', 'width': 10}, 400, 'Invalid encoding'),
        ({'width': 'invalid'}, 400, 'incorrect type'),
        ({'width': -5}, 400, 'Invalid output width or height'),
        ({'height': 'invalid'}, 400, 'incorrect type'),
        ({'height': -5}, 400, 'Invalid output width or height'),
        ({'jpegQuality': 'invalid', 'width': 10}, 400, 'incorrect type'),
        ({'jpegSubsampling': 'invalid', 'width': 10}, 400,
         'incorrect type'),
        ({'left': 'invalid'}, 400, 'incorrect type'),
        ({'right': 'invalid'}, 400, 'incorrect type'),
        ({'top': 'invalid'}, 400, 'incorrect type'),
        ({'bottom': 'invalid'}, 400, 'incorrect type'),
        ({'regionWidth': 'invalid'}, 400, 'incorrect type'),
        ({'regionHeight': 'invalid'}, 400, 'incorrect type'),
        ({'units': 'invalid'}, 400, 'Invalid units'),
        ({'unitsWH': 'invalid'}, 400, 'Invalid units'),
    ]
    for entry in badParams:
        resp = server.request(path='/item/%s/tiles/region' % itemId,
                              user=admin,
                              params=entry[0])
        assert utilities.respStatus(resp) == entry[1]
        assert entry[2] in resp.json['message']

    # Get a small region for testing.  Our test file is sparse, so
    # initially get a region where there is full information.
    params = {'regionWidth': 1000, 'regionHeight': 1000,
              'left': 48000, 'top': 3000}
    resp = server.request(path='/item/%s/tiles/region' % itemId,
                          user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    image = origImage = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JPEGHeader)] == utilities.JPEGHeader

    # We can use base_pixels for width and height and fractions for top and
    # left
    params = {
        'regionWidth': 1000,
        'regionHeight': 1000,
        'left': 48000.0 / tileMetadata['sizeX'],
        'top': 3000.0 / tileMetadata['sizeY'],
        'units': 'fraction',
        'unitsWH': 'base'}
    resp = server.request(path='/item/%s/tiles/region' % itemId,
                          user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image == origImage

    # 0-sized results are allowed
    params = {'regionWidth': 1000, 'regionHeight': 0,
              'left': 48000, 'top': 3000, 'width': 1000, 'height': 1000}
    resp = server.request(path='/item/%s/tiles/region' % itemId,
                          user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert len(image) == 0

    # Test scaling (and a sparse region from our file)
    params = {'regionWidth': 2000, 'regionHeight': 1500,
              'width': 500, 'height': 500, 'encoding': 'PNG'}
    resp = server.request(path='/item/%s/tiles/region' % itemId,
                          user=admin, isJson=False, params=params)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 500
    assert height == 375


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testPixel(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])

    # Test bad parameters
    badParams = [
        ({'left': 'invalid'}, 400, 'incorrect type'),
        ({'top': 'invalid'}, 400, 'incorrect type'),
        ({'units': 'invalid'}, 400, 'Invalid units'),
    ]
    for entry in badParams:
        resp = server.request(path='/item/%s/tiles/pixel' % itemId,
                              user=admin,
                              params=entry[0])
        assert utilities.respStatus(resp) == entry[1]
        assert entry[2] in resp.json['message']

    # Test a good query
    resp = server.request(
        path='/item/%s/tiles/pixel' % itemId, user=admin,
        params={'left': 48000, 'top': 3000})
    assert utilities.respStatus(resp) == 200
    assert resp.json == {'r': 237, 'g': 248, 'b': 242}

    # If it is outside of the image, we get an empty result
    resp = server.request(
        path='/item/%s/tiles/pixel' % itemId, user=admin,
        params={'left': 148000, 'top': 3000})
    assert utilities.respStatus(resp) == 200
    assert resp.json == {}


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testGetTileSource(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    # We should have access via getGirderTileSource
    source = getGirderTileSource(itemId, user=admin)
    image, mime = source.getThumbnail(encoding='PNG', height=200)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader

    # We can also use a file with getTileSource.  The user is ignored.
    imagePath = utilities.externaldata('data/sample_image.ptif.sha512')
    source = getTileSource(imagePath, user=admin, encoding='PNG')
    image, mime = source.getThumbnail(encoding='JPEG', width=200)
    assert image[:len(utilities.JPEGHeader)] == utilities.JPEGHeader

    # Test the level0 thumbnail code path
    image, mime = source.getThumbnail(
        encoding='PNG', width=200, height=100, levelZero=True, fill='blue')
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 200
    assert height == 100


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesLoadModelCache(server, admin, fsAssetstore):
    loadmodelcache.invalidateLoadModelCache()
    token = str(Token().createToken(admin)['_id'])
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    # Now the tile request should tell us about the file.  These are
    # specific to our test file
    resp = server.request(path='/item/%s/tiles' % itemId, token=token)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json
    tileMetadata['sparse'] = 5
    _testTilesZXY(server, admin, itemId, tileMetadata, token=token)
    assert six.next(six.itervalues(loadmodelcache.LoadModelCache))['hits'] > 70


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesModelLookupCache(server, user, admin, fsAssetstore):
    User().load = mock.Mock(wraps=User().load)
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    token = str(Token().createToken(user)['_id'])
    lastCount = User().load.call_count
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          token=token, isJson=False)
    assert utilities.respStatus(resp) == 200
    assert User().load.call_count == lastCount + 1
    lastCount = User().load.call_count
    resp = server.request(path='/item/%s/tiles/zxy/1/0/0' % itemId,
                          token=token, isJson=False)
    assert utilities.respStatus(resp) == 200
    assert User().load.call_count == lastCount


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesDZIEndpoints(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    tileMetadata = resp.json
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    xml = utilities.getBody(resp)
    assert 'Width="%d"' % tileMetadata['sizeX'] in xml
    assert 'Overlap="0"' in xml
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, params={
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    xml = utilities.getBody(resp)
    assert 'Width="%d"' % tileMetadata['sizeX'] in xml
    assert 'Overlap="4"' in xml
    resp = server.request(path='/item/%s/tiles/dzi_files/8/0_0.png' % itemId, params={
        'encoding': 'PNG'
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 228
    assert height == 48
    resp = server.request(path='/item/%s/tiles/dzi_files/8/0_0.png' % itemId, params={
        'encoding': 'PNG',
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    assert utilities.getBody(resp, text=False) == image
    # Test bad queries
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, params={
        'encoding': 'TIFF'
    }, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, params={
        'tilesize': 128
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, params={
        'tilesize': 129
    }, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi.dzi' % itemId, params={
        'overlap': -1
    }, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/8/0_0.png' % itemId, params={
        'tilesize': 128
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    resp = server.request(path='/item/%s/tiles/dzi_files/8/0_0.png' % itemId, params={
        'tilesize': 129
    }, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/8/0_0.png' % itemId, params={
        'overlap': -1
    }, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/0/0_0.png' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/20/0_0.png' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/12/0_3.png' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    resp = server.request(path='/item/%s/tiles/dzi_files/12/15_0.png' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    # Test tile sizes
    resp = server.request(path='/item/%s/tiles/dzi_files/12/0_0.png' % itemId, params={
        'encoding': 'PNG',
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 260
    assert height == 260
    resp = server.request(path='/item/%s/tiles/dzi_files/12/0_1.png' % itemId, params={
        'encoding': 'PNG',
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 260
    assert height == 264
    resp = server.request(path='/item/%s/tiles/dzi_files/12/2_1.png' % itemId, params={
        'encoding': 'PNG',
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 264
    assert height == 264
    resp = server.request(path='/item/%s/tiles/dzi_files/12/14_2.png' % itemId, params={
        'encoding': 'PNG',
        'overlap': 4
    }, user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    (width, height) = struct.unpack('!LL', image[16:24])
    assert width == 68
    assert height == 260


@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesAfterCopyItem(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    file = utilities.uploadTestFile('yb10kx5k.png', admin, fsAssetstore)
    itemId = str(file['itemId'])
    fileId = str(file['_id'])
    tileMetadata = _postTileViaHttp(boundServer, admin, itemId, fileId)
    _testTilesZXY(boundServer, admin, itemId, tileMetadata)
    item = Item().load(itemId, force=True)
    newItem = Item().copyItem(item, admin)
    assert item['largeImage']['fileId'] != newItem['largeImage']['fileId']
    Item().remove(item)
    _testTilesZXY(boundServer, admin, str(newItem['_id']), tileMetadata)


@pytest.mark.plugin('large_image')
def testTilesAutoSetOption(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore,
        name='sample_image.PTIF')
    itemId = str(file['itemId'])
    # We should already have tile information.
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    # Turn off auto-set and try again
    Setting().set(constants.PluginSettings.LARGE_IMAGE_AUTO_SET, 'false')
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 400
    assert 'No large image file' in resp.json['message']
    # Turn it back on
    Setting().set(constants.PluginSettings.LARGE_IMAGE_AUTO_SET, 'true')
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesAssociatedImages(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])

    resp = server.request(path='/item/%s/tiles/images' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json, ['label' == 'macro']
    resp = server.request(path='/item/%s/tiles/images/label' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.JPEGHeader)] == utilities.JPEGHeader
    resp = server.request(
        path='/item/%s/tiles/images/label' % itemId, user=admin,
        isJson=False, params={'encoding': 'PNG', 'width': 256, 'height': 256})
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader
    (width, height) = struct.unpack('!LL', image[16:24])
    assert max(width, height) == 256

    # Test missing associated image
    resp = server.request(path='/item/%s/tiles/images/nosuchimage' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image == b''

    # Test with an image that doesn't have associated images
    file = utilities.uploadExternalFile(
        'data/sample_Easy1.png.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles' % itemId, method='POST', user=admin)
    assert utilities.respStatus(resp) == 200

    resp = server.request(path='/item/%s/tiles/images' % itemId, user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json == []
    resp = server.request(path='/item/%s/tiles/images/nosuchimage' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image = utilities.getBody(resp, text=False)
    assert image == b''


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesWithFrameNumbers(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample.ome.tif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    # Test that we can get frames via either tiles/zxy or tiles/fzxy and
    # that the frames are different
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    image0 = utilities.getBody(resp, text=False)
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False, params={'frame': 0})
    assert utilities.respStatus(resp) == 200
    assert utilities.getBody(resp, text=False) == image0
    resp = server.request(path='/item/%s/tiles/fzxy/0/0/0/0' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    assert utilities.getBody(resp, text=False) == image0
    resp = server.request(path='/item/%s/tiles/zxy/0/0/0' % itemId,
                          user=admin, isJson=False, params={'frame': 1})
    assert utilities.respStatus(resp) == 200
    image1 = utilities.getBody(resp, text=False)
    assert image1 != image0
    resp = server.request(path='/item/%s/tiles/fzxy/1/0/0/0' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    assert utilities.getBody(resp, text=False) == image1


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesHistogram(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(
        path='/item/%s/tiles/histogram' % itemId,
        params={'width': 2048, 'height': 2048, 'resample': False})
    assert len(resp.json) == 3
    assert len(resp.json[0]['hist']) == 256
    assert resp.json[1]['samples'] == 2801664
    assert resp.json[1]['hist'][128] == 176


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testTilesInternalMetadata(server, admin, fsAssetstore):
    file = utilities.uploadExternalFile(
        'data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles/internal_metadata' % itemId)
    assert resp.json['tilesource'] == 'tiff'


@pytest.mark.usefixtures('unbindLargeImage')  # noqa
@pytest.mark.usefixtures('girderWorker')  # noqa
@pytest.mark.plugin('large_image')
def testTilesFromMultipleDotName(boundServer, admin, fsAssetstore, girderWorker):  # noqa
    # Unicode file names shouldn't cause problems when generating tiles.
    file = utilities.uploadTestFile('yb10kx5k.png', admin, fsAssetstore)
    # Our normal testing method doesn't pass through the unicode name
    # properly, so just change it after upload.
    file = File().load(file['_id'], force=True)
    file['name'] = 'A name with...dots.png'
    file = File().save(file)
    fileId = str(file['_id'])

    itemId = str(file['itemId'])
    item = Item().load(itemId, force=True)
    item['name'] = 'A name with...dots.png'
    item = Item().save(item)

    tileMetadata = _postTileViaHttp(boundServer, admin, itemId, fileId)
    assert tileMetadata['tileWidth'] == 256
    assert tileMetadata['tileHeight'] == 256
    assert tileMetadata['sizeX'] == 10000
    assert tileMetadata['sizeY'] == 5000
    assert tileMetadata['levels'] == 7
    assert tileMetadata['magnification'] is None
    assert tileMetadata['mm_x'] is None
    assert tileMetadata['mm_y'] is None
    _testTilesZXY(boundServer, admin, itemId, tileMetadata)
