package nutiteq.bb.ui;

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Graphics;

import com.nutiteq.MapComponent;
import com.nutiteq.components.Line;
import com.nutiteq.components.OnMapElement;
import com.nutiteq.components.Place;
import com.nutiteq.components.PlaceInfo;
import com.nutiteq.components.Polygon;
import com.nutiteq.components.WgsBoundingBox;
import com.nutiteq.components.WgsPoint;
import com.nutiteq.components.ZoomRange;
import com.nutiteq.controls.ControlKeysHandler;
import com.nutiteq.controls.OnScreenZoomControls;
import com.nutiteq.fs.FileSystem;
import com.nutiteq.io.ResourceRequestor;
import com.nutiteq.kml.KmlService;
import com.nutiteq.listeners.ErrorListener;
import com.nutiteq.listeners.MapListener;
import com.nutiteq.listeners.OnMapElementListener;
import com.nutiteq.listeners.PlaceListener;
import com.nutiteq.location.LocationSource;
import com.nutiteq.maps.GeoMap;
import com.nutiteq.net.DownloadCounter;
import com.nutiteq.net.DownloadStreamOpener;
import com.nutiteq.ui.Cursor;
import com.nutiteq.ui.DownloadDisplay;
import com.nutiteq.ui.PanningStrategy;
import com.nutiteq.ui.ZoomIndicator;

public class MapField extends Field implements MapListener {
    private int fieldWidth;
    private int fieldHeight;

    private final MapComponent map;
    private MapListener mapListener;

    private Graphics wrapped;
    private com.nutiteq.wrappers.rimui.Graphics graphicsWrapper;

    public MapField(final String label, final String licenseKey,
            final String vendor, final String appname, final int width,
            final int height, final WgsPoint startPoint, final int zoom) {
        super(Field.FOCUSABLE);
        fieldWidth = width;
        fieldHeight = height;
        map = new MapComponent(licenseKey, vendor, appname, fieldWidth,
                fieldHeight, startPoint, zoom);
        map.setMapListener(this);
        this.setPadding(2, 2, 2, 2);
    }

    /**
     * Initialize needed resources for mapping and start internal threads. This
     * is a required step for application
     */
    public void startMapping() {
        map.startMapping();
    }

    protected boolean navigationClick(int status, int time) {
        fieldChangeNotify(1);
        return true;
    }

    protected void onFocus(int direction) {
        invalidate();
    }

    protected void onUnfocus() {
        invalidate();
    }

    public int getPreferredWidth() {
        return fieldWidth;
    }

    public int getPreferredHeight() {
        return fieldHeight;
    }

    protected void layout(int arg0, int arg1) {
        setExtent(getPreferredWidth(), getPreferredHeight());
    }

    protected void drawFocus(Graphics graphics, boolean on) {

    }

    protected void fieldChangeNotify(int context) {
        try {
            this.getChangeListener().fieldChanged(this, context);
        } catch (Exception e) {
        }
    }

    protected void paint(Graphics g) {
        if (wrapped != g) {
            wrapped = g;
            graphicsWrapper = new com.nutiteq.wrappers.rimui.Graphics(g);
        }

        // paint on wrapper (in effect painting on native graphics)
        map.paint(graphicsWrapper);
        // remove pushContext() done inside library
        graphicsWrapper.popAll();
    }

    /**
     * Define Control Keys codes for map manipulation. For example typical
     * Select key:
     * <code>mapItem.defineControlKey(ControlKeys.SELECT_KEY, -5);</code>
     * 
     * @param keyCode
     *            map component key code, see
     *            {@link com.nutiteq.controls.ControlKeys ControlKeys}
     * @param keyValue
     *            device key code, for some can use Canvas constants like
     *            <code>Canvas.KEY_NUM2</code>
     */
    public void defineControlKey(final int keyCode, final int keyValue) {
        map.defineControlKey(keyCode, keyValue);
    }

    /**
     * Change control keys handler used for actions mapping.
     * 
     * @param keysHandler
     *            new keys mapping handler
     */
    public void setControlKeysHandler(final ControlKeysHandler keysHandler) {
        map.setControlKeysHandler(keysHandler);
    }

    protected void keyPressed(final int keyCode) {
        map.keyPressed(keyCode);
    }

    protected void keyReleased(final int keyCode) {
        map.keyReleased(keyCode);
    }

    protected void keyRepeated(final int keyCode) {
        map.keyRepeated(keyCode);
    }

    protected void pointerDragged(final int x, final int y) {
        map.pointerDragged(x, y);
    }

    protected void pointerPressed(final int x, final int y) {
        map.pointerPressed(x, y);
    }

    protected void pointerReleased(final int x, final int y) {
        map.pointerReleased(x, y);
    }

    /**
     * Zoom in map 1 step
     */
    public void zoomIn() {
        map.zoomIn();
    }

    /**
     * Zoom out map 1 step
     */
    public void zoomOut() {
        map.zoomOut();
    }

    /**
     * Set map listener for receiving callback events from library.
     * &quot;needRepaint&quot; action forwarded from MapListener is used for
     * notifying if displayed map is complete or not.
     * 
     * @param mL
     *            Listener class reference, e.g. <code>this</code>
     */
    public void setMapListener(final MapListener mL) {
        mapListener = mL;
    }

    /**
     * Set Place listener to receive place "mouseover" and selection events
     * 
     * @param pL
     *            Listener class reference, e.g. <code>this</code>
     */
    public void setPlaceListener(final PlaceListener pL) {
        map.setPlaceListener(pL);
    }

    /**
     * Set listener for component error events (connection errors, license
     * errors, parsing errors etc
     * 
     * @param eL
     *            Listener class reference, e.g. <code>this</code>
     */
    public void setErrorListener(final ErrorListener eL) {
        map.setErrorListener(eL);
    }

    /**
     * Set middle point of map
     * 
     * @param wgs
     *            a WgsPoint object
     * @param zoom
     *            zoom level, 0 - world to max zoom (17 typically)
     */
    public void setMiddlePoint(final WgsPoint wgs, final int zoom) {
        map.setMiddlePoint(wgs, zoom);
    }

    /**
     * Set Center point of map
     * 
     * @param lon
     *            longitude, in WGS84 decimal degrees
     * @param lat
     *            latitude, in WGS84 decimal degrees
     * @param zoom
     *            zoom level, 0 - world to max zoom (17 typically)
     */
    public void setMiddlePoint(final double lon, final double lat,
            final int zoom) {
        map.setMiddlePoint(lon, lat, zoom);
    }

    /**
     * Get current center point of map
     * 
     * @return a WgsPoint object
     */
    public WgsPoint getMiddlePoint() {
        return map.getMiddlePoint();
    }

    /**
     * Event for clicking on map (used in MapListener)
     */
    public void mapClicked(final WgsPoint p) {
        if (mapListener != null) {
            mapListener.mapClicked(p);
        }
    }

    /**
     * Event for moving of map (used in MapListener)
     */
    public void mapMoved() {
        if (mapListener != null) {
            mapListener.mapMoved();
        }
    }

    /**
     * Event if map needs repainting (used in MapListener)
     */
    public void needRepaint(final boolean mapIsComplete) {
        invalidate();
        if (mapListener != null) {
            mapListener.needRepaint(mapIsComplete);
        }
    }

    /**
     * Add Place to map
     * 
     * @param place
     *            a Place object
     */
    public void addPlace(final Place place) {
        map.addPlace(place);
    }

    /**
     * Add elements to be displayed on map
     * 
     * @param elements
     *            elements to be added for display
     */
    public void addOnMapElements(final OnMapElement[] elements) {
        map.addOnMapElements(elements);
    }

    /**
     * Add many places to map
     * 
     * @param places
     *            array of Place objects
     */
    public void addPlaces(final Place[] places) {
        map.addPlaces(places);
    }

    /**
     * Remove Place from map
     * 
     * @param place
     *            the Place object to be removed
     */
    public void removePlace(final Place place) {
        map.removePlace(place);
    }

    /**
     * remove several places from map
     * 
     * @param places
     *            array of Place objects
     */
    public void removePlaces(final Place[] places) {
        map.removePlaces(places);
    }

    /**
     * Add single line to map
     * 
     * @param line
     *            a Line object
     */
    public void addLine(final Line line) {
        map.addLine(line);
    }

    /**
     * Add multiple lines to map
     * 
     * @param lines
     *            array of Line object
     * @see Line#Line(WgsPoint[] points, com.nutiteq.components.LineStyle style)
     */
    public void addLines(final Line[] lines) {
        map.addLines(lines);
    }

    /**
     * Remove previously added line
     * 
     * @param line
     *            line to be removed
     */
    public void removeLine(final Line line) {
        map.removeLine(line);
    }

    /**
     * Remove multiple lines
     * 
     * @param lines
     *            lines to be removed
     */
    public void removeLines(final Line[] lines) {
        map.removeLines(lines);
    }

    /**
     * Set zoom controls to be displayed on screen and used for touch screen
     * zooming.
     * 
     * @param controls
     *            zoom controls to be used
     */
    public void setOnScreenZoomControls(final OnScreenZoomControls controls) {
        map.setOnScreenZoomControls(controls);
    }

    /**
     * Show map zoom scale after zoom action. Defaults to false.
     * 
     * @param showInicator
     *            should the zoom indicator be shown
     */
    public void showZoomLevelIndicator(final boolean showInicator) {
        map.showZoomLevelIndicator(showInicator);
    }

    /**
     * Get Bounding Box of current map view
     * 
     * @return a Bounding Box object
     */
    public WgsBoundingBox getBoundingBox() {
        return map.getBoundingBox();
    }

    /**
     * Set bounding box for the view. Finds the best zoom level for the bounding
     * box view.
     * 
     * @param bBox
     *            are to be displayed (in WGS84 coordinates)
     */
    public void setBoundingBox(final WgsBoundingBox bBox) {
        map.setBoundingBox(bBox);
    }

    /**
     * get max and min zoom of current map (typically 0...18)
     * 
     * @return a ZoomRange object
     */
    public ZoomRange getZoomRange() {
        return map.getZoomRange();
    }

    /**
     * Set map zoom without changing position.
     * 
     * @param newZoom
     *            new zoom level
     */
    public void setZoom(final int newZoom) {
        map.setZoom(newZoom);
    }

    /**
     * Get the current zoom level.
     * 
     * @return current zoom level
     */
    public int getZoom() {
        return map.getZoom();
    }

    /**
     * Add KML layer to the map Usage example:
     * <code>mapItem.addKmlService(new KmlUrlReader("http://www.panoramio.com/panoramio.kml?LANG=en_US.utf8",true));</code>
     * 
     * @param service
     *            Reference to KML Service, can be own implementation or use
     *            KmlUrlReader
     * @see com.nutiteq.kml.KmlUrlReader#KmlUrlReader(String, boolean)
     */
    public void addKmlService(final KmlService service) {
        map.addKmlService(service);
    }

    /**
     * List currently added KML Services
     * 
     * @return array of KmlServices
     */

    public KmlService[] getKmlServices() {
        return map.getKmlServices();
    }

    /**
     * Remove previously added kml service
     * 
     * @param service
     *            service to be removed
     */
    public void removeKmlService(final KmlService service) {
        map.removeKmlService(service);
    }

    /**
     * Stop threads started by MapComponent. Called before application exit to
     * clean library resources.
     */
    public void stopMapping() {
        map.stopMapping();
    }

    /**
     * Change base map
     * 
     * @param newMap
     *            reference to the map object
     * @see com.nutiteq.maps.CloudMade#CloudMade(String, int, int)
     */
    public void setMap(final GeoMap newMap) {
        map.setMap(newMap);
    }

    /**
     * Retrieve currently used map.
     * 
     * @return currently displayed map
     */
    public GeoMap getMap() {
        return map.getMap();
    }

    /**
     * Get additional info for internally handled (retrieved from kml service)
     * objects.
     * 
     * @param place
     *            place associated with internal data
     * @return info object with additional data
     */
    public PlaceInfo getAdditionalInfo(final Place place) {
        return map.getAdditionalInfo(place);
    }

    /**
     * Replace the default cursor.
     * 
     * @param newCursor
     *            cursor implementation
     */
    public void setCursor(final Cursor newCursor) {
        map.setCursor(newCursor);
    }

    /**
     * Remove given elements from map display
     * 
     * @param elements
     *            elements to be removed
     */
    public void removeOnMapElements(final OnMapElement[] elements) {
        map.removeOnMapElements(elements);
    }

    /**
     * Get internal log for library.
     * 
     * @return internal log
     */
    public String getLibraryLog() {
        return map.getLibraryLog();
    }

    /**
     * Set download stream opener, that creates connections to downloaded
     * resources
     * 
     * @param opener
     *            opener to be used for resources reading
     */
    public void setDownloadStreamOpener(final DownloadStreamOpener opener) {
        map.setDownloadStreamOpener(opener);
    }

    /**
     * Set panning strategy for map component. If not set, the strategy will
     * default to {@link com.nutiteq.ui.ThreadDrivenPanning}
     * 
     * @param panningStrategy
     *            new panning strategy
     */
    public void setPanningStrategy(final PanningStrategy panningStrategy) {
        map.setPanningStrategy(panningStrategy);
    }

    /**
     * Set search strategy for map tile.
     * 
     * @param tileSearchStrategy
     *            search strategy to be used
     */
    public void setTileSearchStrategy(final GeoMap[] tileSearchStrategy) {
        map.setTileSearchStrategy(tileSearchStrategy);
    }

    /**
     * Set location source with GPS marker to be displayed on map
     * 
     * @param marker
     *            source to be used
     */
    public void setLocationSource(final LocationSource marker) {
        map.setLocationSource(marker);
    }

    /**
     * Remove used location source
     */
    public void removeLocationSource() {
        map.removeLocationSource();
    }

    /**
     * Set cache for networking. Currently cached data is:
     * <ul>
     * <li>map tiles to rms level</li>
     * <li>kml icons to memory and rms</li>
     * </ul>
     * 
     * @param cache
     */
    public void setNetworkCache(final com.nutiteq.cache.Cache cache) {
        map.setNetworkCache(cache);
    }

    public void addPolygon(final Polygon polygon) {
        map.addPolygon(polygon);
    }

    public void addPolygons(final Polygon[] polygons) {
        map.addPolygons(polygons);
    }

    public void removePolygon(final Polygon polygon) {
        map.removePolygon(polygon);
    }

    public void removePolygons(final Polygon[] polygons) {
        map.removePolygons(polygons);
    }

    /**
     * Enqueue new download task to be executed by library.
     * 
     * @param downloadable
     *            resource to be downloaded
     * @param cacheLevel
     *            at which cache levels should response be cached
     */
    public void enqueueDownload(final ResourceRequestor downloadable,
            final int cacheLevel) {
        map.enqueueDownload(downloadable, cacheLevel);
    }

    /**
     * Set zoom indicator to be painted on display
     * 
     * @param zoomIndicator
     *            zoom indicator to use
     */
    public void setZoomLevelIndicator(final ZoomIndicator zoomIndicator) {
        map.setZoomLevelIndicator(zoomIndicator);
    }

    /**
     * Enable network traffic counter using default implementation (
     * {@link com.nutiteq.net.NutiteqDownloadCounter}).
     */
    public void enableDownloadCounter() {
        map.enableDownloadCounter();
    }

    /**
     * Set download counter used for gathering information about network traffic
     * 
     * @param downloadCounter
     *            implementation used
     */
    public void setDownloadCounter(final DownloadCounter downloadCounter) {
        map.setDownloadCounter(downloadCounter);
    }

    public DownloadCounter getDownloadCounter() {
        return map.getDownloadCounter();
    }

    /**
     * Enable network traffic overlay using default implementation for painting
     * ( {@link com.nutiteq.ui.NutiteqDownloadDisplay}).
     */
    public void enableDownloadDisplay() {
        map.enableDownloadDisplay();
    }

    /**
     * Set used implementation for network traffic display on map.
     * 
     * @param display
     *            display used for info show
     */
    public void setDownloadDisplay(final DownloadDisplay display) {
        map.setDownloadDisplay(display);
    }

    public void setFileSystem(final FileSystem fs) {
        map.setFileSystem(fs);
    }

    public void setOnMapElementListener(final OnMapElementListener listener) {
        map.setOnMapElementListener(listener);
    }
}
