/*
 * Copyright (C) 2014.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program 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 for more details.
 */

package uk.me.parabola.mkgmap.osmstyle;

import java.util.List;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.trergn.MapObject;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.TagDict;
import uk.me.parabola.mkgmap.reader.osm.Way;
import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;

/**
 * Class that is used to connect an OSM way with the attributes of GType
 * after Style processing.
 * 
 * @author GerdP
 *
 */
public class ConvertedWay {
	private static final Logger log = Logger.getLogger(ConvertedWay.class);
	private final Way way;				// with tags after Style processing
	private final GType gt;
	private final boolean isRoad;
	private boolean hasDirection;    // if true way should not be reversed in RoadMerger

	private byte roadClass;			// 0-4
	private byte roadSpeed;			// 0-7
	private byte mkgmapAccess; 		// bit mask, see ACCESS_TAGS 
	private final byte routeFlags;	// bit mask, see ROUTING_TAGS
	private boolean reversed;		// points were reversed
	private boolean overlay;		// this is a non-routable overlay line that for a road 
	

	public ConvertedWay(Way way, GType type) {
		this.way = way;
		this.gt = type;
		routeFlags = evalRouteTags(way);
		// note that the gt.getType() may not be a routable type when overlays are used
		if (type.isRoad() && !MapObject.hasExtendedType(gt.getType())) {
			this.roadClass = (byte) gt.getRoadClass();
			this.roadSpeed = (byte) gt.getRoadSpeed();
			recalcRoadClass(way);
			recalcRoadSpeed(way);
			mkgmapAccess = evalAccessTags(way);
			isRoad = true;
		} else {
			roadClass = 0;
			roadSpeed = 0;
			mkgmapAccess = 0;
			isRoad = false;
		}
	}
	
	public ConvertedWay(ConvertedWay other, Way way){
		this.way = way;
		// copy all other attributes
		this.gt = other.gt;
		this.isRoad	 = other.isRoad;
		this.roadClass = other.roadClass;
		this.roadSpeed = other.roadSpeed;
		this.mkgmapAccess = other.mkgmapAccess;
		this.routeFlags = other.routeFlags;
		this.overlay = other.overlay;
		this.hasDirection = other.hasDirection;
	}
	
	public GType getGType(){
		return gt;
	}

	public Way getWay() {
		return way;
	}
	
	public byte getAccess(){
		return mkgmapAccess;
	}
	
	public byte getRoadClass(){
		return roadClass;
	}

	public byte getRoadSpeed(){
		return roadSpeed;
	}
	
	public byte getRouteFlags(){
		return routeFlags;
	}
	

	/**
	 * Recalculates the road class based on the tags
	 * <ul>
	 * <li>{@code mkgmap:road-class}</li>
	 * <li>{@code mkgmap:road-class-min}</li>
	 * <li>{@code mkgmap:road-class-max}</li>
	 * </ul>
	 * The road class is changed if the tags modify its road class. 
	 * 
	 * @param el an element 
	 * @return {@code true} the road class has been changed, else {@code false} 
	 */
	private static final short TKM_ROAD_CLASS = TagDict.getInstance().xlate("mkgmap:road-class");
	public boolean recalcRoadClass(Element el) {
		// save the original road class value
		byte oldRoadClass = roadClass;
		
		String val = el.getTag(TKM_ROAD_CLASS);
		if (val != null) {
			if (val.startsWith("-")) {
				roadClass -= Byte.decode(val.substring(1));
			} else if (val.startsWith("+")) {
				roadClass += Byte.decode(val.substring(1));
			} else {
				roadClass = Byte.decode(val);
			}
		}
		val = el.getTag("mkgmap:road-class-max");
		byte roadClassMax = 4;
		if (val != null)
			roadClassMax = Byte.decode(val);

		val = el.getTag("mkgmap:road-class-min");
		byte roadClassMin = 0;
		if (val != null)
			roadClassMin = Byte.decode(val);

		if (roadClass > roadClassMax)
			roadClass = roadClassMax;
		else if (roadClass < roadClassMin)
			roadClass = roadClassMin;

		return (roadClass != oldRoadClass);
	}
	
	/**
	 * Recalculates the road speed 
	 * <ul>
	 * <li>{@code mkgmap:road-speed-class}</li>
	 * <li>{@code mkgmap:road-speed}</li>
	 * <li>{@code mkgmap:road-speed-min}</li>
	 * <li>{@code mkgmap:road-speed-max}</li>
	 * </ul>
	 * 
	 * @param el an element 
	 * @return {@code true} the road speed has been changed, else {@code false} 
	 */
	private static final short TKM_ROAD_SPEED = TagDict.getInstance().xlate("mkgmap:road-speed");
	private static final short TKM_ROAD_SPEED_CLASS = TagDict.getInstance().xlate("mkgmap:road-speed-class");
	public boolean recalcRoadSpeed(Element el) {
		// save the original road speed value
		byte oldRoadSpeed = roadSpeed;
		
		// check if the road speed is modified
		String roadSpeedOverride = el.getTag(TKM_ROAD_SPEED_CLASS);
		if (roadSpeedOverride != null) {
			try {
				byte rs = Byte.decode(roadSpeedOverride);
				if (rs >= 0 && rs <= 7) {
					// override the road speed class
					roadSpeed = rs;
				} else {
					log.error(el.getDebugName()
							+ " road classification mkgmap:road-speed-class="
							+ roadSpeedOverride + " must be in [0;7]");
				}
			} catch (Exception exp) {
				log.error(el.getDebugName()
						+ " road classification mkgmap:road-speed-class="
						+ roadSpeedOverride + " must be in [0;7]");
			}
		}
		
		// check if the road speed should be modified more
		String val = el.getTag(TKM_ROAD_SPEED);
		if(val != null) {
			if(val.startsWith("-")) {
				roadSpeed -= Byte.decode(val.substring(1));
			}
			else if(val.startsWith("+")) {
				roadSpeed += Byte.decode(val.substring(1));
			}
			else {
				roadSpeed = Byte.decode(val);
			}
		}
		val = el.getTag("mkgmap:road-speed-max");
		byte roadSpeedMax = 7;
		if(val != null)
			roadSpeedMax = Byte.decode(val);

		val = el.getTag("mkgmap:road-speed-min");
		byte roadSpeedMin = 0;
		if(val != null)
			roadSpeedMin = Byte.decode(val);

		if(roadSpeed > roadSpeedMax)
			roadSpeed = roadSpeedMax;
		else if(roadSpeed < roadSpeedMin)
			roadSpeed = roadSpeedMin;
		return (oldRoadSpeed != roadSpeed);
	}

	public List<Coord> getPoints(){
		return way.getPoints();
	}

	public boolean isValid() {
		return way != null && way.getPoints().size() >= 2;
	}
	
	public String toString(){
		return getGType() + " " + getWay().getId() + " " + getWay().toTagString();

	}
	
	public boolean isOneway(){
		return (routeFlags & R_ONEWAY) != 0;
	}

	public boolean isRoundabout(){
		return (routeFlags & R_ROUNDABOUT) != 0;
	}
	public boolean isToll(){
		return (routeFlags & R_TOLL) != 0; 
	}

	public boolean isUnpaved(){
		return (routeFlags & R_UNPAVED) != 0; 
	}

	public boolean isFerry(){
		return (routeFlags & R_FERRY) != 0; 
	}

	public boolean isCarpool(){
		return (routeFlags & R_CARPOOL) != 0; 
	}

	public boolean isThroughroute(){
		return (routeFlags & R_THROUGHROUTE) != 0; 
	}
	
	public boolean isRoad(){
		return isRoad;
	}

	public boolean isReversed() {
		return reversed;
	}

	public void setReversed(boolean reversed) {
		this.reversed = reversed;
	}

	public void setOverlay(boolean b) {
		this.overlay = b;
	}

	public boolean isOverlay() {
		return overlay;
	}

	public void setHasDirection(boolean b) {
		hasDirection = b;
	}
	public boolean hasDirection() {
		return hasDirection;
	}
}
