/*
Copyright (C) 2001, 2006 United States Government as represented by
the Administrator of the National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.layers;

import com.sun.opengl.util.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.Point;

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.logging.Level;

/**
 * @author Paul Collins
 * @version $Id: PlaceNameLayer.java 1788 2007-05-08 17:12:05Z dcollins $
 */
public class PlaceNameLayer extends AbstractLayer
{
    private final PlaceNameServiceSet placeNameServiceSet;
    private final List<Tile[]> tiles = new ArrayList<Tile[]>();

    /**
     * @param placeNameServiceSet the set of PlaceNameService objects that PlaceNameLayer will render.
     * @throws IllegalArgumentException if <code>placeNameServiceSet</code> is null
     */
    public PlaceNameLayer(PlaceNameServiceSet placeNameServiceSet)
    {
        if (placeNameServiceSet == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PlaceNameServiceSetIsNull");
            WorldWind.logger().log(Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        this.placeNameServiceSet = placeNameServiceSet.deepCopy();
        for (int i = 0; i < this.placeNameServiceSet.getServiceCount(); i++)
        {
            tiles.add(i, buildTiles(this.placeNameServiceSet.getService(i)));
        }
    }

    public final PlaceNameServiceSet getPlaceNameServiceSet()
    {
        return this.placeNameServiceSet;
    }

    // ============== Tile Assembly ======================= //
    // ============== Tile Assembly ======================= //
    // ============== Tile Assembly ======================= //

    private static class Tile
    {
        final PlaceNameService placeNameService;
        final Sector sector;
        final int row;
        final int column;
        final int hash;
        // Computed data.
        String fileCachePath = null;
        Extent extent = null;
        double extentVerticalExaggeration = Double.MIN_VALUE;

        static int computeRow(Angle delta, Angle latitude)
        {
            if (delta == null || latitude == null)
            {
                String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
                WorldWind.logger().log(Level.FINE, msg);
                throw new IllegalArgumentException(msg);
            }
            return (int) ((latitude.getDegrees() + 90d) / delta.getDegrees());
        }

        static int computeColumn(Angle delta, Angle longitude)
        {
            if (delta == null || longitude == null)
            {
                String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
                WorldWind.logger().log(Level.FINE, msg);
                throw new IllegalArgumentException(msg);
            }
            return (int) ((longitude.getDegrees() + 180d) / delta.getDegrees());
        }

        static Angle computeRowLatitude(int row, Angle delta)
        {
            if (delta == null)
            {
                String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
                WorldWind.logger().log(Level.FINE, msg);
                throw new IllegalArgumentException(msg);
            }
            return Angle.fromDegrees(-90d + delta.getDegrees() * row);
        }

        static Angle computeColumnLongitude(int column, Angle delta)
        {
            if (delta == null)
            {
                String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
                WorldWind.logger().log(Level.FINE, msg);
                throw new IllegalArgumentException(msg);
            }
            return Angle.fromDegrees(-180 + delta.getDegrees() * column);
        }

        Tile(PlaceNameService placeNameService, Sector sector, int row, int column)
        {
            this.placeNameService = placeNameService;
            this.sector = sector;
            this.row = row;
            this.column = column;
            this.hash = this.computeHash();
        }

        int computeHash()
        {
            return this.getFileCachePath() != null ? this.getFileCachePath().hashCode() : 0;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || this.getClass() != o.getClass())
                return false;

            final Tile other = (Tile) o;

            return this.getFileCachePath() != null ? !this.getFileCachePath().equals(other.getFileCachePath()) :
                other.getFileCachePath() != null;
        }

        Extent getExtent(DrawContext dc)
        {
            if (dc == null)
            {
                String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
                WorldWind.logger().log(Level.FINE, message);
                throw new IllegalArgumentException(message);
            }

            if (this.extent == null || this.extentVerticalExaggeration != dc.getVerticalExaggeration())
            {
                this.extentVerticalExaggeration = dc.getVerticalExaggeration();
                this.extent = Sector.computeBoundingCylinder(dc.getGlobe(), this.extentVerticalExaggeration,
                    this.sector);
            }

            return extent;
        }

        String getFileCachePath()
        {
            if (this.fileCachePath == null)
                this.fileCachePath = this.placeNameService.createFileCachePathFromTile(this.row, this.column);

            return this.fileCachePath;
        }

        PlaceNameService getPlaceNameService()
        {
            return placeNameService;
        }

        java.net.URL getRequestURL() throws java.net.MalformedURLException
        {
            return this.placeNameService.createServiceURLFromSector(this.sector);
        }

        Sector getSector()
        {
            return sector;
        }

        public int hashCode()
        {
            return this.hash;
        }
    }

    private Tile[] buildTiles(PlaceNameService placeNameService)
    {
        final Sector sector = placeNameService.getSector();
        final Angle dLat = placeNameService.getTileDelta().getLatitude();
        final Angle dLon = placeNameService.getTileDelta().getLongitude();

        // Determine the row and column offset from the global tiling origin for the southwest tile corner
        int firstRow = Tile.computeRow(dLat, sector.getMinLatitude());
        int firstCol = Tile.computeColumn(dLon, sector.getMinLongitude());
        int lastRow = Tile.computeRow(dLat, sector.getMaxLatitude().subtract(dLat));
        int lastCol = Tile.computeColumn(dLon, sector.getMaxLongitude().subtract(dLon));

        int nLatTiles = lastRow - firstRow + 1;
        int nLonTiles = lastCol - firstCol + 1;

        Tile[] tiles = new Tile[nLatTiles * nLonTiles];

        Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
        for (int row = firstRow; row <= lastRow; row++)
        {
            Angle p2;
            p2 = p1.add(dLat);

            Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
            for (int col = firstCol; col <= lastCol; col++)
            {
                Angle t2;
                t2 = t1.add(dLon);

                tiles[col + row * nLonTiles] = new Tile(placeNameService, new Sector(p1, p2, t1, t2), row, col);
                t1 = t2;
            }
            p1 = p2;
        }

        return tiles;
    }

    // ============== Place Name Data Structures ======================= //
    // ============== Place Name Data Structures ======================= //
    // ============== Place Name Data Structures ======================= //

    private static class PlaceNameChunk implements Cacheable
    {
        final PlaceNameService placeNameService;
        final StringBuilder textArray;
        final int[] textIndexArray;
        final double[] latlonArray;
        final int numEntries;
        final long estimatedMemorySize;

        PlaceNameChunk(PlaceNameService service, StringBuilder text, int[] textIndices,
            double[] positions, int numEntries)
        {
            this.placeNameService = service;
            this.textArray = text;
            this.textIndexArray = textIndices;
            this.latlonArray = positions;
            this.numEntries = numEntries;
            this.estimatedMemorySize = this.computeEstimatedMemorySize();
        }

        long computeEstimatedMemorySize()
        {
            long result = 0;
            result += BufferUtil.SIZEOF_SHORT * textArray.capacity();
            result += BufferUtil.SIZEOF_INT * textIndexArray.length;
            result += BufferUtil.SIZEOF_DOUBLE * latlonArray.length;
            return result;
        }

        Position getPosition(int index)
        {
            int latlonIndex = 2 * index;
            return Position.fromDegrees(latlonArray[latlonIndex], latlonArray[latlonIndex + 1], 0);
        }

        PlaceNameService getPlaceNameService()
        {
            return this.placeNameService;
        }

        String getText(int index)
        {
            int beginIndex = textIndexArray[index];
            int endIndex = (index + 1 < numEntries) ? textIndexArray[index + 1] : textArray.length();
            return this.textArray.substring(beginIndex, endIndex);
        }

        public long getSizeInBytes()
        {
            return this.estimatedMemorySize;
        }

        Iterator<PlaceName> createRenderIterator(final DrawContext dc)
        {
            return new Iterator<PlaceName>()
            {
                PlaceNameImpl placeNameProxy = new PlaceNameImpl();
                int index = -1;

                public boolean hasNext()
                {
                    return index < (PlaceNameChunk.this.numEntries - 1);
                }

                public PlaceName next()
                {
                    if (!hasNext())
                        throw new NoSuchElementException();
                    this.updateProxy(placeNameProxy, ++index);
                    return placeNameProxy;
                }

                public void remove()
                {
                    throw new UnsupportedOperationException();
                }

                void updateProxy(PlaceNameImpl proxy, int index)
                {
                    proxy.text = getText(index);
                    proxy.position = getPosition(index);
                    proxy.font = placeNameService.getFont();
                    proxy.color = placeNameService.getColor();
                    proxy.visible = isNameVisible(dc, placeNameService, proxy.position);
                }
            };
        }
    }

    private static class PlaceNameImpl implements PlaceName
    {
        String text;
        Position position;
        Font font;
        Color color;
        boolean visible;

        PlaceNameImpl()
        {
        }

        public String getText()
        {
            return this.text;
        }

        public void setText(String text)
        {
            throw new UnsupportedOperationException();
        }

        public Position getPosition()
        {
            return this.position;
        }

        public void setPosition(Position position)
        {
            throw new UnsupportedOperationException();
        }

        public Font getFont()
        {
            return this.font;
        }

        public void setFont(Font font)
        {
            throw new UnsupportedOperationException();
        }

        public Color getColor()
        {
            return this.color;
        }

        public void setColor(Color color)
        {
            throw new UnsupportedOperationException();
        }

        public boolean isVisible()
        {
            return this.visible;
        }

        public void setVisible(boolean visible)
        {
            throw new UnsupportedOperationException();
        }

        public WWIcon getIcon()
        {
            return null;
        }

        public void setIcon(WWIcon icon)
        {
            throw new UnsupportedOperationException();
        }
    }

    // ============== Rendering ======================= //
    // ============== Rendering ======================= //
    // ============== Rendering ======================= //

    private final PlaceNameRenderer placeNameRenderer = new PlaceNameRenderer();

    @Override
    protected void doRender(DrawContext dc)
    {
        final boolean enableDepthTest = dc.getView().getAltitude()
            < (dc.getVerticalExaggeration() * dc.getGlobe().getMaxElevation());

        int serviceCount = this.placeNameServiceSet.getServiceCount();
        for (int i = 0; i < serviceCount; i++)
        {
            PlaceNameService placeNameService = this.placeNameServiceSet.getService(i);
            if (!isServiceVisible(dc, placeNameService))
                continue;

            double minDist = placeNameService.getMinDisplayDistance();
            double maxDist = placeNameService.getMaxDisplayDistance();
            double minDistSquared = minDist * minDist;
            double maxDistSquared = maxDist * maxDist;

            Tile[] tiles = this.tiles.get(i);
            for (Tile tile : tiles)
            {
                try
                {
                    drawOrRequestTile(dc, tile, minDistSquared, maxDistSquared, enableDepthTest);
                }
                catch (Exception e)
                {
                    String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionRenderingTile");
                    WorldWind.logger().log(Level.FINE, message, e);
                }
            }
        }

        this.sendRequests();
    }

    private void drawOrRequestTile(DrawContext dc, Tile tile, double minDisplayDistanceSquared,
        double maxDisplayDistanceSquared, boolean enableDepthTest)
    {
        if (!isTileVisible(dc, tile, minDisplayDistanceSquared, maxDisplayDistanceSquared))
            return;

        Object cacheObj = WorldWind.memoryCache().getObject(tile);
        if (cacheObj == null)
        {
            this.requestTile(this.readQueue, tile);
            return;
        }

        if (!(cacheObj instanceof PlaceNameChunk))
            return;

        PlaceNameChunk placeNameChunk = (PlaceNameChunk) cacheObj;
        Iterator<PlaceName> renderIter = placeNameChunk.createRenderIterator(dc);
        placeNameRenderer.render(dc, renderIter, enableDepthTest);
    }

    private static boolean isServiceVisible(DrawContext dc, PlaceNameService placeNameService)
    {
        if (!placeNameService.isEnabled())
            return false;
        //noinspection SimplifiableIfStatement
        if (dc.getVisibleSector() != null && !placeNameService.getSector().intersects(dc.getVisibleSector()))
            return false;

        return placeNameService.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates());
    }

    private static boolean isTileVisible(DrawContext dc, Tile tile, double minDistanceSquared,
        double maxDistanceSquared)
    {
        if (!tile.getSector().intersects(dc.getVisibleSector()))
            return false;

        Position viewPosition = dc.getView().getPosition();
        Angle lat = clampAngle(viewPosition.getLatitude(), tile.getSector().getMinLatitude(),
            tile.getSector().getMaxLatitude());
        Angle lon = clampAngle(viewPosition.getLongitude(), tile.getSector().getMinLongitude(),
            tile.getSector().getMaxLongitude());
        Point p = dc.getGlobe().computePointFromPosition(lat, lon, 0d);
        double distSquared = dc.getView().getEyePoint().distanceToSquared(p);
        //noinspection RedundantIfStatement
        if (minDistanceSquared > distSquared || maxDistanceSquared < distSquared)
            return false;

        return true;
    }

    private static boolean isNameVisible(DrawContext dc, PlaceNameService service, Position namePosition)
    {
        double elevation = dc.getVerticalExaggeration() * namePosition.getElevation();
        Point namePoint = dc.getGlobe().computePointFromPosition(namePosition.getLatitude(),
            namePosition.getLongitude(), elevation);
        Point eye = dc.getView().getEyePoint();

        double dist = eye.distanceTo(namePoint);
        return dist >= service.getMinDisplayDistance() && dist <= service.getMaxDisplayDistance();
    }

    private static Angle clampAngle(Angle a, Angle min, Angle max)
    {
        return a.compareTo(min) < 0 ? min : (a.compareTo(max) > 0 ? max : a);
    }

    // ============== Image Reading and Downloading ======================= //
    // ============== Image Reading and Downloading ======================= //
    // ============== Image Reading and Downloading ======================= //

    private static final int MAX_REQUESTS = 64;
    private final Queue<Tile> downloadQueue = new LinkedBlockingQueue<Tile>(MAX_REQUESTS);
    private final Queue<Tile> readQueue = new LinkedBlockingQueue<Tile>(MAX_REQUESTS);

    private static class RequestTask implements Runnable
    {
        final PlaceNameLayer layer;
        final Tile tile;

        RequestTask(PlaceNameLayer layer, Tile tile)
        {
            this.layer = layer;
            this.tile = tile;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || this.getClass() != o.getClass())
                return false;

            final RequestTask other = (RequestTask) o;

            // Don't include layer in comparison so that requests are shared among layers
            return !(this.tile != null ? !this.tile.equals(other.tile) : other.tile != null);
        }

        @Override
        public int hashCode()
        {
            return (this.tile != null ? this.tile.hashCode() : 0);
        }

        public void run()
        {
            synchronized (tile)
            {
                if (WorldWind.memoryCache().getObject(this.tile) != null)
                    return;

                final java.net.URL tileURL = WorldWind.dataFileCache().findFile(tile.getFileCachePath(), false);
                if (tileURL != null)
                {
                    if (this.layer.loadTile(this.tile, tileURL))
                    {
                        tile.getPlaceNameService().unmarkResourceAbsent(tile.getPlaceNameService().getTileNumber(
                            tile.row,
                            tile.column));
                        this.layer.firePropertyChange(AVKey.LAYER, null, this);
                    }
                    else
                    {
                        // Assume that something's wrong with the file and delete it.
                        WorldWind.dataFileCache().removeFile(tileURL);
                        tile.getPlaceNameService().markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row,
                            tile.column));
                        String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + tileURL;
                        WorldWind.logger().log(Level.FINE, message);
                    }
                    return;
                }
            }

            this.layer.requestTile(this.layer.downloadQueue, this.tile);
        }

        public String toString()
        {
            return this.tile.toString();
        }
    }

    private static class DownloadPostProcessor implements RetrievalPostProcessor
    {
        final PlaceNameLayer layer;
        final Tile tile;

        private DownloadPostProcessor(PlaceNameLayer layer, Tile tile)
        {
            this.layer = layer;
            this.tile = tile;
        }

        public java.nio.ByteBuffer run(Retriever retriever)
        {
            if (retriever == null)
            {
                String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
                WorldWind.logger().log(Level.FINE, msg);
                throw new IllegalArgumentException(msg);
            }

            if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
                return null;

            if (!(retriever instanceof URLRetriever))
                return null;

            try
            {
                if (retriever instanceof HTTPRetriever)
                {
                    HTTPRetriever httpRetriever = (HTTPRetriever) retriever;
                    if (httpRetriever.getResponseCode() == java.net.HttpURLConnection.HTTP_NO_CONTENT)
                    {
                        // Mark tile as missing to avoid further attempts
                        tile.getPlaceNameService().markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row,
                            tile.column));
                        return null;
                    }
                }

                URLRetriever urlRetriever = (URLRetriever) retriever;
                java.nio.ByteBuffer buffer = urlRetriever.getBuffer();

                synchronized (tile)
                {
                    final java.io.File cacheFile = WorldWind.dataFileCache().newFile(this.tile.getFileCachePath());
                    if (cacheFile == null)
                    {
                        String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
                            + this.tile.getFileCachePath();
                        WorldWind.logger().log(Level.FINE, msg);
                        return null;
                    }

                    if (cacheFile.exists())
                        return buffer; // info is already here; don't need to do anything

                    if (buffer != null)
                    {
                        WWIO.saveBuffer(buffer, cacheFile);
                        this.layer.firePropertyChange(AVKey.LAYER, null, this);
                        return buffer;
                    }
                }
            }
            catch (java.io.IOException e)
            {
                String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionSavingRetrievedFile");
                WorldWind.logger().log(Level.FINE, message + this.tile.getFileCachePath(), e);
            }

            return null;
        }
    }

    private static class GMLPlaceNameSAXHandler extends org.xml.sax.helpers.DefaultHandler
    {
        static final String GML_FEATURE_MEMBER = "gml:featureMember";
        static final String TOPP_FULL_NAME_ND = "topp:full_name_nd";
        static final String TOPP_LATITUDE = "topp:latitude";
        static final String TOPP_LONGITUDE = "topp:longitude";
        final LinkedList<String> qNameStack = new LinkedList<String>();
        boolean inBeginEndPair = false;
        StringBuilder latBuffer = new StringBuilder();
        StringBuilder lonBuffer = new StringBuilder();

        StringBuilder textArray = new StringBuilder();
        int[] textIndexArray = new int[16];
        double[] latlonArray = new double[16];
        int numEntries = 0;

        GMLPlaceNameSAXHandler()
        {
        }

        PlaceNameChunk createPlaceNameChunk(PlaceNameService service)
        {
            return new PlaceNameChunk(service, this.textArray, this.textIndexArray, this.latlonArray, this.numEntries);
        }

        void beginEntry()
        {
            int textIndex = this.textArray.length();
            this.textIndexArray = append(this.textIndexArray, this.numEntries, textIndex);
            this.inBeginEndPair = true;
        }

        void endEntry()
        {
            double lat = this.parseDouble(this.latBuffer);
            double lon = this.parseDouble(this.lonBuffer);
            int numLatLon = 2 * this.numEntries;
            this.latlonArray = this.append(this.latlonArray, numLatLon, lat);
            numLatLon++;
            this.latlonArray = this.append(this.latlonArray, numLatLon, lon);

            this.latBuffer.delete(0, this.latBuffer.length());
            this.lonBuffer.delete(0, this.lonBuffer.length());
            this.inBeginEndPair = false;
            this.numEntries++;
        }

        double parseDouble(StringBuilder sb)
        {
            double value = 0;
            try
            {
                value = Double.parseDouble(sb.toString());
            }
            catch (NumberFormatException e)
            {
                String msg = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
                WorldWind.logger().log(Level.FINE, msg, e);
            }
            return value;
        }

        int[] append(int[] array, int index, int value)
        {
            if (index >= array.length)
                array = this.resizeArray(array);
            array[index] = value;
            return array;
        }

        int[] resizeArray(int[] oldArray)
        {
            int newSize = 2 * oldArray.length;
            int[] newArray = new int[newSize];
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            return newArray;
        }

        double[] append(double[] array, int index, double value)
        {
            if (index >= array.length)
                array = this.resizeArray(array);
            array[index] = value;
            return array;
        }

        double[] resizeArray(double[] oldArray)
        {
            int newSize = 2 * oldArray.length;
            double[] newArray = new double[newSize];
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            return newArray;
        }

        public void characters(char ch[], int start, int length)
        {
            if (!this.inBeginEndPair)
                return;

            String top = this.qNameStack.getFirst();

            StringBuilder sb = null;
            if (TOPP_LATITUDE == top)
                sb = this.latBuffer;
            else if (TOPP_LONGITUDE == top)
                sb = this.lonBuffer;
            else if (TOPP_FULL_NAME_ND == top)
                sb = this.textArray;

            if (sb != null)
                sb.append(ch, start, length);
        }

        public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes)
        {
            String internQName = qName.intern();
            // Don't validate uri, localName or attributes because they aren't used.
            if (GML_FEATURE_MEMBER == internQName)
                this.beginEntry();
            this.qNameStack.addFirst(qName);
        }

        public void endElement(String uri, String localName, String qName)
        {
            String internQName = qName.intern();
            // Don't validate uri or localName because they aren't used.
            if (GML_FEATURE_MEMBER == internQName)
                this.endEntry();
            this.qNameStack.removeFirst();
        }
    }

    private boolean loadTile(Tile tile, java.net.URL url)
    {
        PlaceNameChunk placeNameChunk = readTile(tile, url);
        if (placeNameChunk == null)
            return false;

        WorldWind.memoryCache().add(tile, placeNameChunk);
        return true;
    }

    private static PlaceNameChunk readTile(Tile tile, java.net.URL url)
    {
        java.io.InputStream is = null;

        try
        {
            String path = url.getFile();
            path = path.replaceAll("%20", " "); // TODO: find a better way to get a path usable by FileInputStream

            java.io.FileInputStream fis = new java.io.FileInputStream(path);
            java.io.BufferedInputStream buf = new java.io.BufferedInputStream(fis);
            is = new java.util.zip.GZIPInputStream(buf);

            GMLPlaceNameSAXHandler handler = new GMLPlaceNameSAXHandler();
            javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser().parse(is, handler);
            return handler.createPlaceNameChunk(tile.getPlaceNameService());
        }
        catch (Exception e)
        {
            String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
            WorldWind.logger().log(Level.FINE, message, e);
        }
        finally
        {
            try
            {
                if (is != null)
                    is.close();
            }
            catch (java.io.IOException e)
            {
                String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
                WorldWind.logger().log(Level.FINE, message, e);
            }
        }

        return null;
    }

    private void requestTile(Queue<Tile> queue, Tile tile)
    {
        if (tile.getPlaceNameService().isResourceAbsent(tile.getPlaceNameService().getTileNumber(
            tile.row, tile.column)))
            return;
        if (!queue.contains(tile))
            queue.offer(tile);
    }

    private void sendRequests()
    {
        Tile tile;
        // Send threaded read tasks.
        while (!WorldWind.threadedTaskService().isFull() && (tile = this.readQueue.poll()) != null)
        {
            WorldWind.threadedTaskService().addTask(new RequestTask(this, tile));
        }
        // Send retriever tasks.
        while (!WorldWind.retrievalService().isFull() && (tile = this.downloadQueue.poll()) != null)
        {
            java.net.URL url;
            try
            {
                url = tile.getRequestURL();
            }
            catch (java.net.MalformedURLException e)
            {
                String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToDownloadFile");
                WorldWind.logger().log(Level.FINE, message + tile, e);
                return;
            }
            WorldWind.retrievalService().runRetriever(new HTTPRetriever(url, new DownloadPostProcessor(this, tile)));
        }
    }
}
