/*
 * Copyright (c) 2010, 2011 Itiner.pl. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Itiner designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Itiner in the LICENSE.txt file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.codename1.maps.layers;

import com.codename1.util.MathUtil;
import com.codename1.ui.Graphics;
import com.codename1.maps.Coord;
import com.codename1.maps.Mercator;
import com.codename1.maps.Projection;
import com.codename1.ui.geom.Point;
import com.codename1.maps.providers.MapProvider;
import com.codename1.maps.Tile;

/**
 * This class is responsible for painting arrows that indicates direction
 * of walk on map.
 * 
 * @author Michal Koperski
 */
public class ArrowLinesLayer extends LinesLayer {

    private int arrowSegmentLength = 60;
    private final int minArrowSementLength = 20;
    private int arrowWidth = 5;
    private int arrowHeight = 10;

    /**
     * Constructor with default projection Mercator.
     */
    public ArrowLinesLayer() {
        this(new Mercator(), "");
    }

    /**
     * Constructor with default projection Mercator.
     */
    public ArrowLinesLayer(String name) {
        this(new Mercator(), name);
    }

    /**
     * @inheritDoc
     */
    public ArrowLinesLayer(Projection p, String name) {
        super(p, name);
    }

    /**
     * Paints arrows on each segment. arrowSegmentLength decides how many
     * arrows will be on each segment.
     * @param g
     * @param segment
     * @param tile
     */
    protected void paintSegment(Graphics g, Coord[] segment, Tile tile) {
        super.paintSegment(g, segment, tile);
        int pointsNo = segment.length;
        for (int i = 1; i < pointsNo; i++) {
            Coord start = segment[i - 1];
            Coord end = segment[i];
            Point s = tile.pointPosition(start);
            Point e = tile.pointPosition(end);
            int noOfSegments = calculateLength(s, e) / arrowSegmentLength;
            if (noOfSegments == 0 && calculateLength(s, e) > minArrowSementLength) {
                noOfSegments = 1;
            }
            for (int j = 1; j <= noOfSegments; j++) {
                if (j == 1) {
                    double div = 1.0 / noOfSegments;
                    drawArrow(g, new Point(s.getX(), s.getY()),
                            new Point((int) (div * e.getX() + s.getX() * (1 - div)),
                            (int) (div * e.getY() + s.getY() * (1 - div))));
                } else if (j == noOfSegments) {
                    double div = (noOfSegments - 1) / (noOfSegments * 1.0);
                    drawArrow(g, new Point((int) (div * e.getX() + s.getX() * (1 - div)),
                            (int) (div * e.getY() + s.getY() * (1 - div))),
                            new Point(e.getX(), e.getY()));
                } else {
                    double div = ((j - 1) * 1.0) / noOfSegments;
                    double div2 = (j * 1.0) / noOfSegments;
                    drawArrow(g, new Point((int) (div * e.getX() + s.getX() * (1 - div)),
                            (int) (div * e.getY() + s.getY() * (1 - div))),
                            new Point((int) (div2 * e.getX() + s.getX() * (1 - div2)),
                            (int) (div2 * e.getY() + s.getY() * (1 - div2))));
                }
            }
        }
    }

    /**
     * This method clones arrowHead object which represents arrow and translate
     * it to position on the map.
     * @param g Graphics Object to paint on
     * @param s starting Point 
     * @param e ending Point
     */
    private void drawArrow(Graphics g, Point s, Point e) {
        double aDir = MathUtil.atan2(e.getY() - s.getY(), e.getX() - s.getX());

        if (aDir < -Math.PI / 2) {
            aDir = MathUtil.atan2(s.getY() - e.getY(), s.getX() - e.getX());
            aDir -= Math.PI;
        }
        aDir -= Math.PI / 2;

        ArrowHead arrowHead = new ArrowHead(arrowHeight, arrowWidth);
        arrowHead.rotate(aDir);
        arrowHead.translate(s.getX() + ((e.getX() - s.getX()) / 2), s.getY() + ((e.getY() - s.getY()) / 2));
        arrowHead.paint(g);
    }

    private int calculateLength(Point s, Point e) {
        return (int) Math.sqrt(sqr(s.getX() - e.getX()) + sqr(s.getY() - e.getY()));
    }

    private double sqr(double a) {
        return a * a;
    }

    /**
     * Returns the arrow height in pixels
     * @return the arrow height
     */
    public int getArrowHeight() {
        return arrowHeight;
    }

    /**
     * Sets the arrow height
     * 
     * @param arrowHeight 
     */
    public void setArrowHeight(int arrowHeight) {
        this.arrowHeight = arrowHeight;
    }

    /**
     * Gets the arrow segment length
     * @return segment length
     */
    public int getArrowSegmentLength() {
        return arrowSegmentLength;
    }

    /**
     * Sets the arrow segment length
     * 
     * @param arrowSegmentLength to set
     */
    public void setArrowSegmentLength(int arrowSegmentLength) {
        this.arrowSegmentLength = arrowSegmentLength;
    }

    /**
     * Gets the arrow width in pixels
     * @return the arrow width
     */
    public int getArrowWidth() {
        return arrowWidth;
    }

    /**
     * Sets the arrow width
     * @param arrowWidth to set
     */
    public void setArrowWidth(int arrowWidth) {
        this.arrowWidth = arrowWidth;
    }

    private class ArrowHead {

        private int _height;
        private int _width;
        private Point[] _nodes;

        ArrowHead(int height, int width) {
            _height = height;
            _width = width;
            _nodes = new Point[3];
            _nodes[0] = new Point(0, 0);
            _nodes[1] = new Point(_width, -_height);
            _nodes[2] = new Point(-_width, -_height);
        }

        public int getHeight() {
            return _height;
        }

        public int getWidth() {
            return _width;
        }

        public Point[] getNodes() {
            return _nodes;
        }

        public void rotate(double aDir) {
            for (int i = 0; i < _nodes.length; i++) {
                _nodes[i] = new Point(rotateX(_nodes[i].getX(), _nodes[i].getY(), aDir),
                        rotateY(_nodes[i].getX(), _nodes[i].getY(), aDir));
            }
        }

        private int rotateX(int x, int y, double aDir) {
            return (int) ((x * Math.cos(aDir)) - (y * Math.sin(aDir)));
        }

        private int rotateY(int x, int y, double aDir) {
            return (int) ((x * Math.sin(aDir)) + (y * Math.cos(aDir)));
        }

        public void translate(int x, int y) {
            for (int i = 0; i < _nodes.length; i++) {
                _nodes[i] = new Point(_nodes[i].getX() + x, _nodes[i].getY() + y);
            }
        }

        public void paint(Graphics g) {
            g.fillTriangle(_nodes[0].getX(), _nodes[0].getY(),
                    _nodes[1].getX(), _nodes[1].getY(),
                    _nodes[2].getX(), _nodes[2].getY());
        }
    }
}
