/*
 * Copyright (C) 2012.
 *
 * 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.reader.osm.boundary;

import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.build.Locator;
import uk.me.parabola.mkgmap.osmstyle.NameFinder;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.TagDict;
import uk.me.parabola.mkgmap.reader.osm.Tags;
import uk.me.parabola.mkgmap.reader.osm.boundary.Boundary;
import uk.me.parabola.util.EnhancedProperties;

/**
 * Allows to extract boundary tags into BoundaryLocationInfo.  
 * Uses a locator if possible, else defaults. 
 * The locator is only needed when used with the LocationHook, utilities like the 
 * BoundaryPreparer will work without it.
 * @author GerdP
 *
 */
public class BoundaryLocationPreparer {
	private static final Logger log = Logger.getLogger(BoundaryLocationPreparer.class);

	private final Locator locator;
	private static final Pattern COMMA_OR_SEMICOLON_PATTERN = Pattern.compile("[,;]+");
	private final NameFinder nameFinder;

	/**
	 * Create a preparer. 
	 * @param props The program properties or null. 
	 */
	public BoundaryLocationPreparer(EnhancedProperties props) {
		locator = (props != null) ? new Locator(props) : null;
		nameFinder = new NameFinder(props);
	}

	/**
	 * Extract location relevant information from tags
	 * @param tags the Tags of a boundary 
	 * @return a new BoundaryLocationInfo instance 
	 */
	public BoundaryLocationInfo parseTags(Tags tags){
		String zip = getZip(tags);
		int admLevel = getAdminLevel(tags);
		String name = getName(tags);
		if (admLevel == 2 && locator != null) {
			String isoCode = locator.addCountry(tags);
			if (isoCode != null) {
				name = isoCode;
			} else {
				log.warn("Country name",name,"not in locator config. Country may not be assigned correctly.");
			}
			log.debug("Coded:",name);
		}
		return new BoundaryLocationInfo(admLevel, name, zip);
	}

	/**
	 * Extract location relevant information from tags of the element
	 * @param el the element
	 * @return a new BoundaryLocationInfo instance 
	 */
	public BoundaryLocationInfo parseTags(Element el){
		return parseTags(el.getCopyOfTags());
	}

	/** 
	 * Extract and prepare tag infos from BoundaryList 
	 * @param boundaries list of boundaries
	 * @return A Map that maps boundary Ids to the location relevant tags
	 */
	public HashMap<String, BoundaryLocationInfo> getPreparedLocationInfo(
			List<Boundary> boundaries) {
		HashMap<String, BoundaryLocationInfo> preparedLocationInfo = new HashMap<String, BoundaryLocationInfo> ();
		if (boundaries != null){
			for (Boundary b :boundaries){
				preparedLocationInfo.put(b.getId(), parseTags(b.getTags())); 
			}
		}
		return preparedLocationInfo;
	}
	
	/**
	 * Try to extract the name of the boundary. 
	 * @param tags the boundary tags
	 * @return a name or null if no usable name tag was found
	 */
	private String getName(Tags tags) {
		if ("2".equals(tags.get(admin_levelTagKey))) {
			// admin_level=2 boundaries. They need to be handled special because their name is changed 
			// to the 3 letter ISO code using the Locator class and the LocatorConfig.xml file. 
			for (short nameTagKey : Locator.PREFERRED_NAME_TAG_KEYS) {
				String nameTagValue = tags.get(nameTagKey);
				if (nameTagValue != null) {
					return getFirstPart(nameTagValue);
				}
			}
		}
		String name = nameFinder.getName(tags);
		if (name != null)
			return getFirstPart(name);
		return null;
	}

	private static String getFirstPart(String name) {
		String[] nameParts = COMMA_OR_SEMICOLON_PATTERN.split(name);
		if (nameParts.length > 0)
			return nameParts[0].trim().intern();
		return null;
	}
	
	private static final short postal_codeTagKey = TagDict.getInstance().xlate("postal_code");
	private static final short boundaryTagKey = TagDict.getInstance().xlate("boundary");
	/**
	 * Try to extract a zip code from the the tags of a boundary. 
	 * @param tags the boundary tags
	 * @return null if no zip code was found, else a String that should be a zip code. 
	 */
	private String getZip(Tags tags) {
		String zip = tags.get(postal_codeTagKey);
		if (zip == null) {
			if ("postal_code".equals(tags.get(boundaryTagKey))){
				// unlikely
				String name = tags.get("name"); 
				if (name == null) {
					name = getName(tags);
				}
				if (name != null) {
					String[] nameParts = name.split(Pattern.quote(" "));
					if (nameParts.length > 0) {
						zip = nameParts[0].trim();
					}
				}
			}
		}
		return zip;
	}

	public static final int UNSET_ADMIN_LEVEL = 100; // must be higher than real levels
	private static final short admin_levelTagKey = TagDict.getInstance().xlate("admin_level");
	/**
	 * translate the admin_level tag to an integer. 
	 * @param tags the boundary tags
	 * @return the admin_level value. The value is UNSET_ADMIN_LEVEL if 
	 * the conversion failed. 
	 */
	private static int getAdminLevel(Tags tags) {
		if ("administrative".equals(tags.get(boundaryTagKey))) {
			String level = tags.get(admin_levelTagKey);
			if (level != null) {
				try {
					int res = Integer.parseInt(level);
					if (res >= 2 && res <= 11)
						return res;
				} catch (NumberFormatException nfe) {
				}
			}
		}
		return UNSET_ADMIN_LEVEL;
	}
} 

