package net.osmand.plus.activities;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Application;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Handler;
import android.text.format.DateFormat;
import android.util.Log;
import android.widget.Toast;

import net.osmand.Algoritms;
import net.osmand.FavouritePoint;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.LogUtil;
import net.osmand.plus.FavouritesDbHelper;
import net.osmand.plus.NavigationService;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.PoiFiltersHelper;
import net.osmand.plus.ProgressDialogImplementation;
import net.osmand.plus.R;
import net.osmand.plus.ResourceManager;
import net.osmand.plus.render.NativeOsmandLibrary;
import net.osmand.plus.render.RendererRegistry;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.voice.CommandPlayer;
import net.osmand.plus.voice.CommandPlayerException;
import net.osmand.plus.voice.CommandPlayerFactory;
import net.osmand.render.RenderingRulesStorage;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class OsmandApplication extends Application {
	public static final String EXCEPTION_PATH = ResourceManager.APP_DIR + "exception.log"; //$NON-NLS-1$
	private static final org.apache.commons.logging.Log LOG = LogUtil.getLog(OsmandApplication.class);
	
	ResourceManager manager = null; 
	PoiFiltersHelper poiFilters = null;
	RoutingHelper routingHelper = null;
	FavouritesDbHelper favorites = null;
	CommandPlayer player = null;
	OsmandSettings osmandSettings;
	DayNightHelper daynightHelper;
	NavigationService navigationService;
	RendererRegistry rendererRegistry;
	
	
	// start variables
	private ProgressDialogImplementation startDialog;
	private List<String> startingWarnings;
	private Handler uiHandler;
	private GPXFile gpxFileToDisplay;
	
	private boolean applicationInitializing = false;
	private Locale prefferedLocale = null;

	
    @Override
	public void	onCreate(){
    	super.onCreate();
    	long timeToStart = System.currentTimeMillis();
    	osmandSettings = OsmandSettings.getOsmandSettings(this);
    	routingHelper = new RoutingHelper(osmandSettings, OsmandApplication.this, player);
    	manager = new ResourceManager(this);
    	daynightHelper = new DayNightHelper(this);
    	uiHandler = new Handler();
    	rendererRegistry = new RendererRegistry();
    	checkPrefferedLocale();
    	startApplication();
    	if(LOG.isDebugEnabled()){
    		LOG.debug("Time to start application " + (System.currentTimeMillis() - timeToStart) + " ms. Should be less < 800 ms");
    	}
	}
    
    @Override
    public void onTerminate() {
    	super.onTerminate();
    	if (routingHelper != null) {
    		routingHelper.getVoiceRouter().onApplicationTerminate(getApplicationContext());
    	}
    }
    
    
    public RendererRegistry getRendererRegistry() {
		return rendererRegistry;
	}
    
    public OsmandSettings getSettings() {
		return osmandSettings;
	}
    
	public PoiFiltersHelper getPoiFilters() {
    	if(poiFilters == null){
    		poiFilters = new PoiFiltersHelper(this);
    	}
		return poiFilters;
	}
	
	public void setGpxFileToDisplay(GPXFile gpxFileToDisplay) {
		this.gpxFileToDisplay = gpxFileToDisplay;
		if(gpxFileToDisplay == null){
			getFavorites().setFavoritePointsFromGPXFile(null);
		} else {
			List<FavouritePoint> pts = new ArrayList<FavouritePoint>();
			for (WptPt p : gpxFileToDisplay.points) {
				FavouritePoint pt = new FavouritePoint();
				pt.setLatitude(p.lat);
				pt.setLongitude(p.lon);
				if(p.name == null){
					p.name = "";
				}
				pt.setName(p.name);
				pts.add(pt);
			}
			getFavorites().setFavoritePointsFromGPXFile(pts);
		}
	}
	
	public GPXFile getGpxFileToDisplay() {
		return gpxFileToDisplay;
	}
    
    public FavouritesDbHelper getFavorites() {
    	if(favorites == null) {
    		favorites = new FavouritesDbHelper(this);
    	}
		return favorites;
	}
    
    public ResourceManager getResourceManager() {
		return manager;
	}
    
    public DayNightHelper getDaynightHelper() {
		return daynightHelper;
	}
	
	@Override
	public void onLowMemory() {
		super.onLowMemory();
		manager.onLowMemory();
	}
	
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		if (prefferedLocale != null) {
			newConfig.locale = prefferedLocale;
			Locale.setDefault(prefferedLocale);
			getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
		}
	}
	
	public void checkPrefferedLocale() {
        Configuration config = getBaseContext().getResources().getConfiguration();
        String lang = osmandSettings.PREFERRED_LOCALE.get();
		if (!"".equals(lang) && !config.locale.getLanguage().equals(lang)) {
			prefferedLocale = new Locale(lang);
			Locale.setDefault(prefferedLocale);
			config.locale = prefferedLocale;
			getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
		}
		
	}
	
 
	public static final int PROGRESS_DIALOG  = 5;

	/**
	 * @param activity that supports onCreateDialog({@link #PROGRESS_DIALOG}) and returns @param progressdialog
	 * @param progressDialog - it should be exactly the same as onCreateDialog
	 * @return
	 */
	public void checkApplicationIsBeingInitialized(Activity activity, ProgressDialog progressDialog){
		// start application if it was previously closed
		startApplication();
		synchronized (OsmandApplication.this) {
			if(startDialog != null){
				progressDialog.setTitle(getString(R.string.loading_data));
				progressDialog.setMessage(getString(R.string.reading_indexes));
				activity.showDialog(PROGRESS_DIALOG);
				startDialog.setDialog(progressDialog);
			} else if (startingWarnings != null) {
				showWarnings(startingWarnings, activity);
			}
		}
	}
	
	public boolean isApplicationInitializing(){
		return startDialog != null;
	}
	
	public RoutingHelper getRoutingHelper() {
		return routingHelper;
	}
	
	public CommandPlayer getPlayer() {
		return player;
	}
	

	public void showDialogInitializingCommandPlayer(final Activity uiContext){
		showDialogInitializingCommandPlayer(uiContext, true);
	}
	
	public void showDialogInitializingCommandPlayer(final Activity uiContext, boolean warningNoneProvider){
		showDialogInitializingCommandPlayer(uiContext, warningNoneProvider, null);
	}
	public void showDialogInitializingCommandPlayer(final Activity uiContext, boolean warningNoneProvider, Runnable run){
		String voiceProvider = osmandSettings.VOICE_PROVIDER.get();
		if (voiceProvider == null || OsmandSettings.VOICE_PROVIDER_NOT_USE.equals(voiceProvider)) {
			if (warningNoneProvider && voiceProvider == null) {
				Builder builder = new AlertDialog.Builder(uiContext);
				builder.setCancelable(true);
				builder.setNegativeButton(R.string.default_buttons_cancel, null);
				builder.setPositiveButton(R.string.default_buttons_ok, new DialogInterface.OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						Intent intent = new Intent(uiContext, SettingsActivity.class);
						intent.putExtra(SettingsActivity.INTENT_KEY_SETTINGS_SCREEN, SettingsActivity.SCREEN_NAVIGATION_SETTINGS);
						uiContext.startActivity(intent);
					}
				});
				builder.setTitle(R.string.voice_is_not_available_title);
				builder.setMessage(R.string.voice_is_not_available_msg);
				builder.show();
			}

		} else {
			if(player == null 
					|| !Algoritms.objectEquals(voiceProvider, player.getCurrentVoice())){
				initVoiceDataInDifferentThread(uiContext, voiceProvider, run);
			}
		}
		
	}

	private void initVoiceDataInDifferentThread(final Activity uiContext, final String voiceProvider, final Runnable run) {
		final ProgressDialog dlg = ProgressDialog.show(uiContext,
				getString(R.string.loading_data),
				getString(R.string.voice_data_initializing));
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					if (player != null) {
						player.clear();
					}
					player = CommandPlayerFactory.createCommandPlayer(voiceProvider, OsmandApplication.this, uiContext);
					routingHelper.getVoiceRouter().setPlayer(player);
					dlg.dismiss();
					if(run != null && uiContext != null){
						uiContext.runOnUiThread(run);
					}
				} catch (CommandPlayerException e) {
					dlg.dismiss();
					showWarning(uiContext, e.getError());
				}
			}
		}).start();
	}
	
	public NavigationService getNavigationService() {
		return navigationService;
	}
	
	public void setNavigationService(NavigationService navigationService) {
		this.navigationService = navigationService;
	}
	
	public synchronized void closeApplication(){
		if(applicationInitializing){
			manager.close();
		}
		applicationInitializing = false; 
	}
	

	public synchronized void startApplication() {
		if (applicationInitializing) {
			return;
		}
		applicationInitializing = true;
		startDialog = new ProgressDialogImplementation(this, null, false);

		startDialog.setRunnable("Initializing app", new Runnable() { //$NON-NLS-1$
					@Override
					public void run() {
						startApplicationBackground();
					}
				});
		startDialog.run();

		Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler());

	}
	
	private void startApplicationBackground() {
		List<String> warnings = null;
		try {
			if (osmandSettings.NATIVE_RENDERING.get()) {
				startDialog.startTask(getString(R.string.init_native_library), -1);
				RenderingRulesStorage storage = rendererRegistry.getCurrentSelectedRenderer();
				boolean initialized = NativeOsmandLibrary.getLibrary(storage) != null;
				if (!initialized) {
					LOG.info("Native library could not loaded!");
				}
			}
			warnings = manager.reloadIndexes(startDialog);
			player = null;
			SavingTrackHelper helper = new SavingTrackHelper(OsmandApplication.this);
			if (helper.hasDataToSave()) {
				startDialog.startTask(getString(R.string.saving_gpx_tracks), -1);
				warnings.addAll(helper.saveDataToGpx());
			}
			helper.close();
		} finally {
			synchronized (OsmandApplication.this) {
				final ProgressDialog toDismiss;
				if (startDialog != null) {
					toDismiss = startDialog.getDialog();
				} else {
					toDismiss = null;
				}
				startDialog = null;

				if (toDismiss != null) {
					uiHandler.post(new Runnable() {
						@Override
						public void run() {
							if (toDismiss.getOwnerActivity() != null) {
								toDismiss.getOwnerActivity().dismissDialog(PROGRESS_DIALOG);
							}
						}
					});
					showWarnings(warnings, toDismiss.getContext());
				} else {
					startingWarnings = warnings;
				}
			}
		}
	}
	
	protected void showWarnings(List<String> warnings, final Context uiContext) {
		if (warnings != null && !warnings.isEmpty()) {
			final StringBuilder b = new StringBuilder();
			boolean f = true;
			for (String w : warnings) {
				if(f){
					f = false;
				} else {
					b.append('\n');
				}
				b.append(w);
			}
			showWarning(uiContext, b.toString());
		}
	}

	private void showWarning(final Context uiContext, final String b) {
		uiHandler.post(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(uiContext, b.toString(), Toast.LENGTH_LONG).show();
			}
		});
	}
	

	private class DefaultExceptionHandler implements UncaughtExceptionHandler {

		private UncaughtExceptionHandler defaultHandler;

		public DefaultExceptionHandler() {
			defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		}

		@Override
		public void uncaughtException(final Thread thread, final Throwable ex) {
			File file = osmandSettings.extendOsmandPath(EXCEPTION_PATH);
			try {
				ByteArrayOutputStream out = new ByteArrayOutputStream();
				PrintStream printStream = new PrintStream(out);
				ex.printStackTrace(printStream);
				StringBuilder msg = new StringBuilder();
				msg.append("Exception occured in thread " + thread.toString() + " : "). //$NON-NLS-1$ //$NON-NLS-2$
						append(DateFormat.format("MMMM dd, yyyy h:mm:ss", System.currentTimeMillis())).append("\n"). //$NON-NLS-1$//$NON-NLS-2$
						append(new String(out.toByteArray()));

				if (file.getParentFile().canWrite()) {
					BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
					writer.write(msg.toString());
					writer.close();
				}
				defaultHandler.uncaughtException(thread, ex);
			} catch (Exception e) {
				// swallow all exceptions
				Log.e(LogUtil.class.getName(), "Exception while handle other exception", e); //$NON-NLS-1$
			}

		}
	}
	
	
}
