package dk.network42.osmfocus;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.res.Configuration;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.WindowManager;
import android.widget.Toast;

public class MainActivity extends Activity implements
				LocationListener, GpsStatus.Listener,
				SensorEventListener {
	
    static final int PREFERENCE_REQUEST   = 9001;
	static final int INVALIDATE_VIEW      = 1000;
	static final int POLL_NOTIFICATIONS   = 1001;

	private static final int LOCATION_INTERVAL = 1000; //ms
	private static final float LOCATION_DISTANCE = 1f; //meters

	public static final String userAgent = "OSMfocus";

	private static final String TAG = "OsmFocusActivity";
	public static final String PREFS_NAME = "OSMFocusPrefsFile";

	LocationManager mLocationManager;
	
	SensorManager sensorManager;
	private Sensor sensorAccelerometer;
	private Sensor sensorMagneticField;
	private float[] valuesAccelerometer;
	private float[] valuesMagneticField;
	private float[] matrixR;
	private float[] matrixI;
	private float[] matrixValues;
	private MapView mapView;
    double mPanLon, mPanLat;
	private OsmServer mOsmServer = new OsmServer(null, userAgent);
	private GestureDetectorCompat mGestureDetector;
	private ScaleGestureDetector mScaleGestureDetector;
	private boolean mScaleInProgress = false;
    SharedData mG = null;
	public double mLonLastUpd=0, mLatLastUpd=0;  // Where mapview was last updated

    NotificationManager mNotificationManager;
	Handler mHandler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        SharedData.checkAppUpdate(getApplicationContext());
        PreferenceManager.setDefaultValues(this, R.xml.preferences, true);
        
        //setContentView(R.layout.activity_main);
        //getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
        //        WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
        		WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        //requestWindowFeature(Window.FEATURE_NO_TITLE);
        getActionBar().setDisplayShowTitleEnabled(false);

        Log.i(TAG, "API level "+Build.VERSION.SDK_INT);

		mHandler = new Handler(Looper.getMainLooper()) {
			int mNotifId;
			boolean mNotifActive;
			@Override
	        public void handleMessage(Message inputMessage) {
				switch (inputMessage.what) {
				case INVALIDATE_VIEW:
					//Log.d(TAG, "Invalidate view");
					mapView.invalidate();
					// Fall-through
				case POLL_NOTIFICATIONS:
					int dlsb = mG.mTileLayerProvider.getActiveDownloads();
					int dlsv = mG.mVectorLayerProvider.getActiveDownloads();
					Log.d(TAG, "Downloads active="+dlsb+"+"+dlsv);
					if (dlsb>0 || dlsv>0) {
						if (! mNotifActive) {
							String st = "Downloading...";
							mNotifId = setOsmLoadNotif(st);
							mNotifActive = true;
						} else {
							Log.d(TAG, "Notification already active");
							mHandler.sendMessageDelayed(mHandler.obtainMessage(POLL_NOTIFICATIONS, this), 1000);
						}
					} else if (mNotifActive) {
						cancelOsmLoadNotif(mNotifId);
						mNotifActive = false;
					}
					break;
				}
			}
		};

        mapView = new MapView(this);
        //mG.mPcfg.update(getBaseContext());
        setContentView(mapView);
        mapView.requestFocus();
        if (getLastNonConfigurationInstance() != null) {
        	Log.d(TAG, "getLastNonConfigurationInstance() != null");
        	mG = (SharedData) getLastNonConfigurationInstance();
            mapView.setSharedData(mG);
            mG.mTileLayer.setMainHandler(mHandler);
            mG.mVectorLayer.setMainHandler(mHandler);
            mG.update(getApplicationContext());
        } else {
	        long maxMemL = Runtime.getRuntime().maxMemory();
	        int maxMem = (int) Math.min(maxMemL, Integer.MAX_VALUE);
	        Log.i(TAG, "maxMemory="+maxMem);
	        mG = new SharedData();
            mapView.setSharedData(mG);
	        mG.mTileLayerProvider = new OsmTileProvider(mG.mOsmServerAgentName, OsmTile.MAX_DOWNLOAD_THREADS);
	        mG.mTileLayer = new OsmTileLayerBm(mG.mTileLayerProvider, maxMem/4);
	        mG.mTileLayer.setAttrib(getApplicationContext().getString(R.string.info_osm_copyright));
            mG.mTileLayer.setMainHandler(mHandler);
            mG.mTileLayer.setProviderUrl(OsmTileLayer.urlFromType(mG.mPcfg.mBackMapType));
	        mG.mVectorLayerProvider = new OsmTileProvider(mG.mOsmServerAgentName);
	        mG.mVectorLayer = new OsmTileLayerVector(mG.mVectorLayerProvider, maxMem/8);
	        mG.mVectorLayer.setSharedData(mG);
            mG.mVectorLayer.setMainHandler(mHandler);
	        mG.update(getApplicationContext());
        }

        if (mG.mDeveloperMode) {
	        if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
	            Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(mG, ""));
	        }
        }

        mGestureDetector = new GestureDetectorCompat(this, mGestureListener);
        mScaleGestureDetector = new ScaleGestureDetector(this, mScaleGestureListener);
        /*mDetector.setOnDoubleTapListener(this);*/
        
        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mLocationManager.addGpsStatusListener(this);
        Location loc = getMostRecentKnownLocation();
        if (loc != null)
        	this.onLocationChanged(loc);  

		if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
			mLocationManager.requestLocationUpdates(
					LocationManager.GPS_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE, this);
		} else if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
			mLocationManager.requestLocationUpdates(
					LocationManager.NETWORK_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE, this);
		}

        if (mG.mUseCompass) {
        	sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        	sensorAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        	sensorMagneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        	valuesAccelerometer = new float[3];
        	valuesMagneticField = new float[3];
        	matrixR = new float[9];
        	matrixI = new float[9];
        	matrixValues = new float[3];
        }
        
        //registerForContextMenu(mapView);
        
        registerComponentCallbacks(new ComponentCallbacks2() {
        	@ Override
        	public void onTrimMemory(int level)  {
        		appTrimMemory(level);
        	}
        	public void onLowMemory() {
                appTrimMemory(TRIM_MEMORY_COMPLETE);
        	}
        	public void onConfigurationChanged(Configuration newConfig) {
        		//
        	}
        });
    }

    public void appTrimMemory(int level) {
        Log.d(TAG, "appTrimMemory("+level+")");
        if (mG.mTileLayer != null)
        	mG.mTileLayer.onTrimMemory(level);
        if (mG.mVectorLayer != null)
        	mG.mVectorLayer.onTrimMemory(level);    	
    }

    public Object onRetainNonConfigurationInstance() {
    	if (mG != null)
    		return mG;
    	return super.onRetainNonConfigurationInstance();
    }
    
    /*
     * Called when the Activity becomes visible.
     */
    @Override
    protected void onStart() {
        super.onStart();
    }

    /*
     * Called when the Activity is no longer visible.
     */
    @Override
    protected void onStop() {
        super.onStop();
    }
    
    // Define a DialogFragment that displays the error dialog
    public static class ErrorDialogFragment extends DialogFragment {
        // Global field to contain the error dialog
        private Dialog mDialog;
        // Default constructor. Sets the dialog field to null
        public ErrorDialogFragment() {
            super();
            mDialog = null;
        }
        // Set the dialog to display
        public void setDialog(Dialog dialog) {
            mDialog = dialog;
        }
        // Return a Dialog to the DialogFragment.
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case PREFERENCE_REQUEST:
    			mG.update(getBaseContext());
    			mapView.postInvalidate();                          
            	break;
        }
    }
    
    public static void showOkDialog(Context context, String txt)
    {
        Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(txt);
        builder.setCancelable(true);
        builder.setPositiveButton("OK", null);
        AlertDialog dialog = builder.create();
        dialog.show();
    }

    @Override 
    public boolean onTouchEvent(MotionEvent event){
        boolean h = mScaleGestureDetector.onTouchEvent(event);
        h |= mGestureDetector.onTouchEvent(event);
        h |= super.onTouchEvent(event);
        return h;
    }

    private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
    = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    	@Override
	    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
            float focusX = scaleGestureDetector.getFocusX();
            float focusY = scaleGestureDetector.getFocusY();
	    	Log.d(TAG, "ScaleBegin, focus="+focusX+","+focusY);
	    	return true;
	    }
    	@Override
    	public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
            float focusX = scaleGestureDetector.getFocusX();
            float focusY = scaleGestureDetector.getFocusY();
            float scale = scaleGestureDetector.getScaleFactor();
	    	Log.d(TAG, "Scale, scale="+scale+", focus="+focusX+","+focusY);
	    	mG.mPcfg.setScale(mG.mPcfg.getScale()*scale, mG.mCtx);
	    	mScaleInProgress = true;
	    	mapView.postInvalidate();
	    	return true;
	    }
    	@Override
	    public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
	    	Log.d(TAG, "ScaleEnd");
	    }
    };
    
    private final GestureDetector.SimpleOnGestureListener mGestureListener
    = new GestureDetector.SimpleOnGestureListener() {
    	@Override
    	public boolean onDown(MotionEvent event) { 
            //Log.d(TAG,"onDown: " + event.toString());
            mPanLon = mG.mLon; mPanLat = mG.mLat;
	    	mScaleInProgress = false;
	    	return true;
        }
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                float distanceY) {
    		if (mScaleInProgress) {
                Log.d(TAG, "Scroll skipped");
    			return false;
    		}
            //Log.d(TAG, "onScroll: " + e1.toString()+e2.toString());
    		float dx, dy;
    		dx = e2.getX()-e1.getX();
    		dy = e2.getY()-e1.getY();
            Log.d(TAG, "Scroll pixels: x="+dx+" y="+dy);
            mG.mLon = mPanLon - dx/(mG.mPcfg.mScale*MapView.prescale);
            double panMercLat = dy/(mG.mPcfg.mScale*MapView.prescale);
            mG.mLat = GeoMath.mercatorToLat((GeoMath.latToMercator(mPanLat)+panMercLat));
            Log.d(TAG, "Scroll to lon="+mG.mLon+" lat="+mG.mLat+" ("+GeoMath.latToMercator(mPanLat)+"+"+panMercLat+")");
            mG.mFollowGPS = false;
            mapView.postInvalidate();
            return true;
        }
        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, 
                float velocityX, float velocityY) {
            Log.d(TAG, "onFling: " + event1.toString()+event2.toString());
            return true;
        }

//        @Override
//        public void onLongPress(MotionEvent event) {
//            Log.d(TAG, "onLongPress: " + event.toString());
//            openOptionsMenu();
//        }
        //@Override
        //public void onShowPress(MotionEvent event) {
        //    Log.d(TAG, "onShowPress: " + event.toString());
        //}
        
        //@Override
        //public boolean onSingleTapUp(MotionEvent event) {
        //    Log.d(TAG, "onSingleTapUp: " + event.toString());
        //    return true;
       // }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	getMenuInflater().inflate(R.menu.main_activity_actions, menu);
    	if (mG.mDeveloperMode) {
	    	menu.add(0, R.id.action_testdownload, Menu.NONE, R.string.action_testdownload);
	    	menu.add(0, R.id.action_togglehud, Menu.NONE, R.string.action_togglehud);
    	}
        return super.onCreateOptionsMenu(menu);
    }

    //@Override
    //public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    //	super.onCreateContextMenu(menu, v, menuInfo);
    //    getMenuInflater().inflate(R.menu.main, menu);
    //}

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	int itemId = item.getItemId();
		if (itemId == R.id.action_download) {
			if (mG.mPhyLocation != null) {
				Log.d(TAG, "Download");
				//float horizon = 250f; //meters
				//GeoBBox bbox = OsmServer.getBoxForPoint(mG.mLat, mG.mLon, horizon);
				//GeoBBox bbox = OsmTile.pos2BBox(mG.mLon, mG.mLat, 16);
				//downloadBox(bbox);
				// FIXME
				mG.mVectorLayer.download(mG.mLon, mG.mLat);
			} else {
		    	Toast toast = Toast.makeText(getApplicationContext(), getBaseContext().getString(R.string.info_locationunknown), Toast.LENGTH_SHORT);
		        toast.show();
			}
			return true;
//		} else if (itemId == R.id.action_testdownload) {
//			Log.d(TAG, "Test Download");
//			float horizon = 250f; //meters
//			GeoBBox bbox = OsmServer.getBoxForPoint(mG.mLat, mG.mLon, horizon);
//			olddownloadBox(bbox);
//			return true;
//		} else if (itemId == R.id.action_loadcache) {
//			Log.d(TAG, "Load Cache");
//			readOSMFile("cache.osm");
//			return true;
		} else if (itemId == R.id.action_whereami) {
			Log.d(TAG, "Where am I?");
			mG.mFollowGPS = true;
			mG.mPcfg.setScale(PaintConfig.DEFAULT_ZOOM, mG.mCtx);
			if (mG.mPhyLocation != null) {
				mG.mLat = mG.mPhyLocation.getLatitude();
				mG.mLon = mG.mPhyLocation.getLongitude();
				mapView.postInvalidate();
			} else {
		    	Toast toast = Toast.makeText(getApplicationContext(), getBaseContext().getString(R.string.info_locationunknown), Toast.LENGTH_SHORT);
		        toast.show();
			}
			return true;
		} else if (itemId == R.id.action_togglehud) {
			mG.mDebugHUD = !mG.mDebugHUD;
			mapView.invalidate();
			return true;
		} else if (itemId == R.id.action_settings) {
			startActivityForResult(new Intent(this, SettingsActivity.class), PREFERENCE_REQUEST);
			mG.update(getApplicationContext());
			return true;
		} else {
			return super.onOptionsItemSelected(item);
		}
    }
    
    @Override
    protected void onResume() {
    	super.onResume();
    	//locationManager.requestLocationUpdates(mG.mLocProvider, 1000/*ms*/, 1/*meters*/, this);
    	if (mG.mUseCompass) {
    		sensorManager.registerListener(this,
    				sensorAccelerometer,
    				SensorManager.SENSOR_DELAY_NORMAL);
    		sensorManager.registerListener(this,
    				sensorMagneticField,
    				SensorManager.SENSOR_DELAY_NORMAL);
    	}
    }

    @Override
    protected void onPause() {
    	super.onPause();
    	if (mG.mUseCompass) {
    		sensorManager.unregisterListener(this,
    				sensorAccelerometer);
    		sensorManager.unregisterListener(this,
    				sensorMagneticField);
    	}
    }

    protected Location getMostRecentKnownLocation() {
    	Location loc = null;
    	long besttime = 0;
    	List<String> allp = mLocationManager.getAllProviders();
    	for (String p: allp) {
    		Location l = mLocationManager.getLastKnownLocation(p);
    		if (l != null) {
    			long time = l.getTime();
    			if (time>besttime) {
    				loc = l;
    				besttime = time;
    			}
    		}
    	}
    	return loc;
    }

    @Override
    public void onLocationChanged(Location location) {
    	mG.mPhyLocation = location;
		mG.mLocationUpdates++;
		double curr_lon = mG.mPhyLocation.getLongitude();
		double curr_lat = mG.mPhyLocation.getLatitude();
    	if (mG.mFollowGPS) {
    		mG.mLon = curr_lon;
    		mG.mLat = curr_lat;
    	}
        //Log.d(TAG, "onLocationChanged, Loc="+mG.mPhyLocation);
        final double noise = 0.00002;
        if (Math.abs(mLonLastUpd-curr_lon)>noise || Math.abs(mLatLastUpd-curr_lat)>noise) {
        	mLonLastUpd = curr_lon;
        	mLatLastUpd = curr_lat;
        	mapView.postInvalidate();
        }
    }

	@Override
	public void onProviderDisabled(String provider) {
		//Log.e(TAG, "onProviderDisabled: " + provider);
	}

	@Override
	public void onProviderEnabled(String provider) {
		//Log.e(TAG, "onProviderEnabled: " + provider);
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		//Log.e(TAG, "onStatusChanged: " + provider);
	}

    public void onGpsStatusChanged(int event) {
    	boolean statchg = false;
    	switch (event) {
    	case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
	    	GpsStatus gstat = mLocationManager.getGpsStatus(null);
	        if (gstat != null) {
	        	Iterable<GpsSatellite> satellites = gstat.getSatellites();
	        	Iterator<GpsSatellite> sat = satellites.iterator();
	        	int num = 0, used = 0;
	        	while (sat.hasNext()) {
	        		num++;
	        		GpsSatellite satellite = sat.next();
	        		if (satellite.usedInFix()) {
	        			used++;
	        		}
	        	}
	        	if (mG.mSatelitesVisible != num || mG.mSatelitesUsed != used)
	        		statchg = true;
	        	mG.mSatelitesVisible = num;
	        	mG.mSatelitesUsed = used;
	        }
	        break;
    	}
    	if (statchg)
    		mapView.postInvalidate();
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

    	switch(event.sensor.getType()){
    	case Sensor.TYPE_ACCELEROMETER:
    		for(int i =0; i < 3; i++){
    			valuesAccelerometer[i] = event.values[i];
    		}
    		break;
    	case Sensor.TYPE_MAGNETIC_FIELD:
    		for(int i =0; i < 3; i++){
    			valuesMagneticField[i] = event.values[i];
    		}
    		break;
    	}
       
    	boolean success = SensorManager.getRotationMatrix(
    			matrixR,
    			matrixI,
    			valuesAccelerometer,
    			valuesMagneticField);
       
    	if(success){
    		SensorManager.getOrientation(matrixR, matrixValues);
        
    		mG.mAzimuth = Math.toDegrees(matrixValues[0]);
    		mG.mPitch = Math.toDegrees(matrixValues[1]);
    		mG.mRoll = Math.toDegrees(matrixValues[2]);
    		//Log.d(TAG, "Orientation: Azimuth " + azimuth + ", Pitch " + pitch + ", Roll " + roll);
    		mapView.postInvalidate();

    	}
    }

    int setOsmLoadNotif(String info) {
		int notifyID = 1;
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(/*this*/getApplicationContext())
		.setContentTitle("Downloading OSM Data")
		.setContentText(info)
		.setSmallIcon(R.drawable.ic_downloadosm);
//		Intent resultIntent = new Intent(/*this*/getApplicationContext(), MainActivity.class); //FIXME
//		PendingIntent resultPendingIntent =
//				PendingIntent.getActivity(
//						/*this*/getApplicationContext(),
//						0,
//						resultIntent,
//						PendingIntent.FLAG_UPDATE_CURRENT
//						);
//		mBuilder.setContentIntent(resultPendingIntent);
		mNotificationManager.notify(
				notifyID,
				mBuilder.build());
		Log.d(TAG, "Set OSM notif, id="+notifyID);
		return notifyID;
    }
    
    void cancelOsmLoadNotif(int notifyID) {
		Log.d(TAG, "Clear OSM notif, id="+notifyID);
    	mNotificationManager.cancel(notifyID);
    }

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}
}
