package org.aykit.owncloud_notes;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;

import org.aykit.MyOwnNotes.R;
import org.aykit.owncloud_notes.classes.MySimpleCursorLoader;
import org.aykit.owncloud_notes.sql.NotesOpenHelper;
import org.aykit.owncloud_notes.sql.NotesTable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;

public class NoteListActivity 
	extends ListActivity 
	implements LoaderCallbacks<Cursor>
{
	public static final String TAG = NoteListActivity.class.getSimpleName();
	
	
	private NotesOpenHelper notesOpenHelper;
	private SQLiteDatabase sqlDatabase;
	private SimpleCursorAdapter simpleCursorAdapter;
	private LoaderManager loaderManager;
	private SharedPreferences settings;
	private Menu theMenu;
	private boolean connectionError;
	
	private final String apiPath = "/index.php/apps/notes/api/v0.2/notes";
	
	/**
	 * used to turn extensive logcat messages on or off. 
	 * debugOn == true -> show more log messages
	 * debugOn == false -> show only essential messages
	 * edited in SettingsActivity
	 */
	private boolean debugOn;
	
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_note_list);
		
		updateSettings();
		debugOn = settings.getBoolean(SettingsActivity.PREF_EXTENSIVE_LOG, false); 
		
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(SettingsActivity.PREF_MENU_INFLATED, false); //this is done to save the fact that menus are not inflated yet.
		editor.putBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, false);
		editor.commit();
		
		loaderManager = getLoaderManager();
		notesOpenHelper = new NotesOpenHelper(this);
	}
	
	@Override
	protected void onResume()
	{
		super.onResume();
		updateSettings();
		if(settings.getBoolean(SettingsActivity.PREF_AUTOSYNC, true) &&				//autosync must be on
				settings.getBoolean(SettingsActivity.PREF_INITIALIZED, false) &&	//the settings (serveraddress, username, password) must be entered
				settings.getBoolean(SettingsActivity.PREF_MENU_INFLATED, false) ) 	//because we need to check whether the menu has been inflated or not
		{																		  	//synchronizeNotes() accesses the menu. if menu is not inflated and access is tried -> NullPointerException
			if ( ! settings.getBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, false) ) //only start sync if there is no sync already in progress. set by synchronizeNotes() and updateDatabase()
			{
				synchronizeNotes();
			}
		}
		
		//tell settings, that a single note was not created yet.
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean("wasCreatedBefore", false);
		editor.putBoolean("wasPaused", false);
		editor.putBoolean("isEditable", false);
		editor.putString("content", "");
		editor.putLong("id", 0);
		editor.putString("status", "");
		editor.commit();
		
		makeSureSqlDatabaseIsOpen();
		
		showAndFillListView();
	}
	
	@Override
	protected void onDestroy()
	{
		super.onDestroy();
		
		updateSettings();
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(SettingsActivity.PREF_MENU_INFLATED, false); // just to make sure, it is set to false next time activity is started
		editor.commit();
	}
	
	@Override
	protected void onPause()
	{
		super.onPause();
		if(sqlDatabase != null)
		{
			if(sqlDatabase.isOpen() && ! sqlDatabase.inTransaction() )
			{
				sqlDatabase.close();
				
				if(notesOpenHelper != null)
				{
					notesOpenHelper.close();
				}
			}
		}
	}
	
	/**
	 * checks whether <code>notesOpenHelper</code> is open and
	 * whether <code>sqlDatabase</code> is open
	 * and makes sure, that both are.
	 */
	public void makeSureSqlDatabaseIsOpen ()
	{
		if(notesOpenHelper == null)
		{
			notesOpenHelper = new NotesOpenHelper(this);
		}
		
		if(sqlDatabase == null)
		{
			sqlDatabase = notesOpenHelper.getWritableDatabase();
		}
		else
		{
			if( ! sqlDatabase.isOpen() )
			{
				sqlDatabase = notesOpenHelper.getWritableDatabase();
			}
		}
	}
	
	@SuppressWarnings("deprecation")
	private void showAndFillListView()
	{
		makeSureSqlDatabaseIsOpen();
		
		String[] from = { NotesTable.CLOUMN_CONTENT, NotesTable.COLUMN_STATUS };
		int[] to = {R.id.textview_note_row_content, R.id.textview_note_row_marked };
		simpleCursorAdapter = new SimpleCursorAdapter(this, R.layout.note_listview_row, null, from, to);
		
		if(loaderManager.getLoader(1) != null) 
		{
			loaderManager.destroyLoader(1);
		}
		
		loaderManager.initLoader(1, null, this);
		
		setListAdapter(simpleCursorAdapter);
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.note_list, menu);
		this.theMenu = menu;
		
		//save the fact that the Menu has been inflated
		updateSettings();
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(SettingsActivity.PREF_MENU_INFLATED, true);
		editor.commit();
		//then synchronize the notes at startup
		if(settings.getBoolean(SettingsActivity.PREF_AUTOSYNC, true) &&				//autosync must be on
				settings.getBoolean(SettingsActivity.PREF_INITIALIZED, false)  ) 	//because we need to check whether the menu has been inflated or not
		{
			if( ! settings.getBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, false) )
			{
				synchronizeNotes();
			}
		}
		
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected( MenuItem item ) {
	    // Handle presses on the action bar items
		Intent intent = null;
		
	    switch (item.getItemId() ) 
	    {
	        case R.id.action_new:
	            //make new note
	        	if (debugOn)
	        	{
	        		Log.d(TAG, "menu: create new noten");
	        	}
	        	intent = new Intent(this, NoteSingleActivity.class);
	        	intent.putExtra("isNewNote", true);
	        	startActivity(intent);
	            return true;
	            
	        case R.id.action_sync:
	        	//start synchronizing
	        	if(debugOn) 
	        	{
	        		Log.d(TAG, "menu: start sync");
	        	}
	        	
	        	if( ! settings.getBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, false) )
	        	{
	        		synchronizeNotes();
	        	}
	        	else
	        	{
	        		Toast.makeText(this, R.string.toast_sync_in_progress, Toast.LENGTH_SHORT).show();
	        	}
	        	return true;
	        	
	        case R.id.action_settings:
	            //go to settings
	        	if(debugOn)
	        	{
	        		Log.d(TAG, "menu: open settings");
	        	}
	        	intent = new Intent(this, SettingsActivity.class);
	        	startActivity(intent);
	            return true;
	            
	        case R.id.action_help:
	        	//show help
	        	intent = new Intent(this, HelpActivity.class);
	        	startActivity(intent);
	        	return true;
	        	
	        case R.id.action_about:
	        	//show about
	        	intent = new Intent(this, AboutActivity.class);
	        	startActivity(intent);
	       
	        default:
	            return super.onOptionsItemSelected(item);
	    }
	}
	
	@Override 
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Do something when a list item is clicked
		Intent intent = new Intent(this, NoteSingleActivity.class);
		intent.putExtra("isNewNote", false); //tell intent that no new note must be created
		
		String content = 	((TextView) v.findViewById(R.id.textview_note_row_content)).getText().toString();
		String status = 	((TextView) v.findViewById(R.id.textview_note_row_marked)).getText().toString();
		
		intent.putExtra("content", content);
		intent.putExtra("id", id);
		intent.putExtra("status", status);
		 
		startActivity(intent);
	}
	
	public void updateSettings()
	{
		settings = PreferenceManager.getDefaultSharedPreferences(this);
	}
	
	/**
	 * <li>shows the progress-bar (calls <code>showProgressBar()</code> )</li>
	 * <li>updates settings (calls <code>updateSettings()</code> )</li>
	 * <li>saves to settings that a sync is in progress (saves boolean to settings)</li>
	 */
	private void setSyncInProgress()
	{
		showProgressBar();
		updateSettings();
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, true);
		editor.commit();
	}
	
	/**
	 * <li>hides the progress-bar (calls <code>hideProgressBar()</code> )</li>
	 * <li>updates settings (calls <code>updateSettings()</code>)</li>
	 * <li>saves to settings that no sync is in progress (saves boolean to settings)</li>
	 */
	private void setSyncNotInProgress()
	{
		updateSettings();
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(SettingsActivity.PREF_SYNC_IN_PROGRESS, false);
		editor.commit();
		hideProgressBar();
	}
	
	/**
	 * checks if there is an active internet connection available.
	 * <br \>
	 * uses <code>ConnectivityManager</code> to check
	 * 
	 * @return	true, iff connected to the internet
	 */
	private boolean hasInternetConnection()
	{
		ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(NoteListActivity.CONNECTIVITY_SERVICE);
		if (connectivityManager.getActiveNetworkInfo() != null )
		{
			if( connectivityManager.getActiveNetworkInfo().isAvailable() && 
				connectivityManager.getActiveNetworkInfo().isConnected()  )
			{
				return true;
			}
			else
			{
				return false;
			}
			
		} 
		else 
		{
			return false;
		}
	}
	
	/**
	 * initiates synchronization with ownCloud server.
	 * follows this order:
	 * <li>upload all new notes (marked <code>NEW_NOTE</code>)</li>
	 * <li>update all modified notes (marked <code>TO_UPDATE</code>)</li>
	 * <li>delete all notes marked <code>TO_DELETE</code></li>
	 * <li>download all notes</li>
	 */
	public void synchronizeNotes()
	{
		//push new notes
		//push notes changes
		//push delete notes
		//get all notes
		Log.d(TAG, "starting note synchonization");
		setSyncInProgress();
		
		connectionError = false;
		
		String serverUrl = settings.getString(SettingsActivity.PREF_ADDRESS, "https://www.example.com");	//defaultvalue = "https://www.example.com"
		String urlToConnect = "";
		String basePath = "";
		
		try
		{
			//create basePath
			URL tempUrl = new URL(serverUrl);
			//must be like: https://user:[email protected]/index.php/apps/notes/api/v0.2/notes
			
			if(tempUrl.getPort() == -1) //no port was given
			{
				basePath = tempUrl.getHost() + tempUrl.getPath() + apiPath;
				if(debugOn)
				{
					Log.d(TAG, "basePath no port: " + basePath);
				}
			}
			else //port was given
			{ 	
				basePath = tempUrl.getHost() + ":" + tempUrl.getPort() + tempUrl.getPath() + apiPath;
				if(debugOn)
				{
					Log.d(TAG, "basePath with port: " + basePath);
				}
			}
			
			urlToConnect = "https://" + basePath;  //this string will be passed to the async tasks
			
			if(debugOn)
			{
				Log.d(TAG, "urlToConnect:" + urlToConnect);
			}
		}
		catch(MalformedURLException e)
		{
			connectionError = true;
			e.printStackTrace();
			if(debugOn)
        	{
				Log.e(TAG, "tempUrl malforemd: serverUrl=" + serverUrl);
        	}
		}
		
		//only proceed if no problems occurred
		if(hasInternetConnection() )
		{
			if (!connectionError)
			{
				//update modified notes
				writeModifiedNotesToServer(urlToConnect);
				
				if(!connectionError)
				{
					//delete marked notes
					deleteMarkedNotesFromServer(urlToConnect);
				}
				
				if(!connectionError)
				{
					//upload new notes
					writeNewNotesToServer(urlToConnect);
				}
				
				//get all notes
				if(!connectionError)
				{
					Log.d(TAG, "getting notes from server");
					new DownloadNotesTask().execute(urlToConnect);
					//rest done in updateDatabase(), which is called when download is finished.
				}
			}
			else
			{
				Toast.makeText(this, R.string.toast_connection_error, Toast.LENGTH_LONG).show();
				setSyncNotInProgress();
			}
		}
		else
		{
			Toast.makeText(this, R.string.toast_no_internet_connection, Toast.LENGTH_LONG).show();
			setSyncNotInProgress();
		}
	}
	
	private void showProgressBar()
	{
		MenuItem item = theMenu.findItem(R.id.action_sync);
		item.setActionView(R.layout.progressbar);
	}
	
	private void hideProgressBar()
	{
		MenuItem item = theMenu.findItem(R.id.action_sync);
		item.setActionView(null);
	}
	
	private void showSSLAlert()
	{
		AlertDialog.Builder builder = new AlertDialog.Builder(this ); 
		
		builder.setMessage(R.string.alert_ssl_cert_not_trusted);
		
		builder.setPositiveButton(R.string.alert_answer_yes, new DialogInterface.OnClickListener() {
	           public void onClick(DialogInterface dialog, int id) {
	               // User clicked Yes button
	        	   // open link to tutorial
	        	   Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://aykit.org/sites/myownnotes.html") );
	        	   
	        	   startActivity(intent);
	           }
	       }
		);
		builder.setNegativeButton(R.string.alert_answer_no_thanks, new DialogInterface.OnClickListener() {
	           public void onClick(DialogInterface dialog, int id) {
	               // User cancelled the dialog
	           }
	       }
		);
		
		AlertDialog dialog = builder.create();
		dialog.show();

	}
	
	public void updateDatabase(String result)
	{
		//update the database with "result" (= a json with _all_ notes)
		//Toast.makeText(this, result, Toast.LENGTH_LONG).show();
		makeSureSqlDatabaseIsOpen();
		
		notesOpenHelper.emptyTheDatabase(sqlDatabase);
		
		try
		{
			JSONArray jsonArray = new JSONArray(result);
			
			//fill contentvalues to be put in sqlite database
			for(int i = 0; i < jsonArray.length(); i++)
			{
				JSONObject jsonObject = jsonArray.getJSONObject(i);
				ContentValues values = new ContentValues();
				
				long id = jsonObject.getLong("id");
				String content = "";
				if(	jsonObject.has("content") && 
					! jsonObject.getString("content").isEmpty()  
					)
				{
					content = jsonObject.getString("content");
				}
				else
				{
					content = "";
				}
				
				//Log.d(TAG, "CONTENT:" + content);
				
				values.put(NotesTable.COLUMN_ID, id);
				values.put(NotesTable.CLOUMN_CONTENT, content);
				
				sqlDatabase.insert(NotesTable.NOTES_TABLE_NAME, null, values);
			}
		}
		catch(JSONException jsonE)
		{
			//something went wrong with json
			Toast.makeText(this, R.string.toast_not_correct_json, Toast.LENGTH_LONG).show();
			jsonE.printStackTrace();
			Log.e(TAG, "no correct JSON data returned from server. result from server:" + result);
		}
		
		setSyncNotInProgress();
		showAndFillListView(); //refresh listview
	}
	
	public void writeNewNotesToServer(String urlToServer)
	{
		Log.d(TAG, "writing new notes to server");
		//upload all notes with COLUMN_STATUS = NEW_NOTE
		Cursor cursor = getCursor(NotesTable.NEW_NOTE);
		
		if(debugOn)
    	{
			int rows = cursor.getCount();
			Log.d(TAG, "cursor rows new notes:" + rows);
    	}
		
		while(!cursor.isAfterLast() )
		{
			String content = cursor.getString(cursor.getColumnIndex(NotesTable.CLOUMN_CONTENT));
			
			//check string for "
			if(content.indexOf('"') != -1)
			{
				content = content.replace("\"", "\\\"");
			}
			
			String toPost = "{ content: \"" + content + "\"}";
			//Log.d(TAG, "to post:" + toPost);
			
			new UploadNotesTask().execute(urlToServer, toPost);
			
			cursor.moveToNext();
		}
		cursor.close();
	}
	
	private void writeModifiedNotesToServer(String urlToServer)
	{
		Log.d(TAG, "writing modified notes to server");
		//upload changes to existing notes marked COLUMN_STATUS = TO_UPDATE
		Cursor cursor = getCursor(NotesTable.TO_UPDATE);
		
		if(debugOn)
    	{
			int rows = cursor.getCount();
			Log.d(TAG, "cursor rows modified notes:" + rows);
    	}
		
		while ( !cursor.isAfterLast() )
		{
			String content = cursor.getString(cursor.getColumnIndex(NotesTable.CLOUMN_CONTENT));
			
			//check string for "
			if(content.indexOf('"') != -1)
			{
				content = content.replace("\"", "\\\"");
			}
			
			long id = cursor.getLong( cursor.getColumnIndex(NotesTable.COLUMN_ID) );
			String urlToServerWithNoteId = urlToServer + "/" + id;
			String toPost = "{ content: \"" + content + "\"}";
			
			new UpdateNotesTask().execute(urlToServerWithNoteId, toPost);
			cursor.moveToNext();
		}
		cursor.close();
	}
	
	private void deleteMarkedNotesFromServer(String urlToServer)
	{
		Log.d(TAG, "deleting notes from server");
		//delete all notes with COLUM_STATUS = TO_DELETE
		Cursor cursor = getCursor(NotesTable.TO_DELETE);
		
		if(debugOn)
    	{
			int rows = cursor.getCount();
			Log.d(TAG, "cursor rows to delete:" + rows);
    	}
		
		while( !cursor.isAfterLast() )
		{
			long id = cursor.getLong( cursor.getColumnIndex(NotesTable.COLUMN_ID));
			String urlToServerWithNoteId = urlToServer + "/" + id;
			
			new DeleteNotesTask().execute(urlToServerWithNoteId);
			cursor.moveToNext();
		}
		cursor.close();
	}
	
	private Cursor getCursor(String status)
	{
		makeSureSqlDatabaseIsOpen();
		
		String selection = NotesTable.COLUMN_STATUS + " = ?";
		String[] selectionArgs = new String[1];
		
		if(status.equals(NotesTable.TO_DELETE))
		{
			selectionArgs[0] = NotesTable.TO_DELETE;
		}
		else if (status.equals(NotesTable.TO_UPDATE))
		{
			selectionArgs[0] = NotesTable.TO_UPDATE ;
		}
		else
		{
			selectionArgs[0] = NotesTable.NEW_NOTE;
		}
		
		Cursor cursor = sqlDatabase.query(NotesTable.NOTES_TABLE_NAME, 
				NotesTable.COLUMNNAMES,
				selection,
				selectionArgs, 
				null, 
				null, 
				null);
		
		cursor.moveToFirst();
		
		return cursor;
	}
	
	/**
	 * deletes a note identified by an id from the sql database.
	 * usually called after a connection error occurred and the server says that a certain note does no longer exist.
	 * 
	 * @param idToDelete	String containing the id of the note that has to be deleted
	 */
	private void removeNoteFromSqlDatabase(String idToDelete)
	{
		makeSureSqlDatabaseIsOpen();
		
		String whereClause = NotesTable.COLUMN_ID + " = ?";
		String[] whereArgs = { idToDelete };
		sqlDatabase.delete(NotesTable.NOTES_TABLE_NAME, 
							whereClause, 
							whereArgs
						   );
	}
	
	
	
	//----------------------------------
	//LoaderManagerMethods. required by interface "LoaderCallbacks<Cursor>"
	//----------------------------------
	@Override
	public Loader<Cursor> onCreateLoader(int id, Bundle args) {
		MySimpleCursorLoader mySimpleCursorLoader;
		String[] projection = NotesTable.COLUMNNAMES;
		
		makeSureSqlDatabaseIsOpen();
		
		mySimpleCursorLoader = new MySimpleCursorLoader(this, sqlDatabase, projection);
		
		
		return mySimpleCursorLoader;
	}

	@Override
	public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
		simpleCursorAdapter.swapCursor(data);	
	}

	@Override
	public void onLoaderReset(Loader<Cursor> loader) {
		simpleCursorAdapter.swapCursor(null);
	}
	
	
	//----------------------------------
	//AsyncTask for doing the deleting
	//----------------------------------
	private class DeleteNotesTask extends AsyncTask<String, Void, String> 
	{
		protected String doInBackground(String... strings) 
		{
			URL url = null;
			HttpsURLConnection urlConnection = null;
			String urlString = strings[0];

			try
			{
				url = new URL(urlString);
				urlConnection = (HttpsURLConnection) url.openConnection();

				urlConnection.setRequestMethod("DELETE");
				urlConnection.setUseCaches(false);
				
				String auth = settings.getString(SettingsActivity.PREF_USERNAME, "username") + ":" + settings.getString(SettingsActivity.PREF_PASSWOORD, "password");
				String basicAuth = "Basic " + new String(Base64.encode(auth.getBytes(), Base64.NO_WRAP));
				urlConnection.setRequestProperty("Authorization", basicAuth);
				
				
				if (Build.VERSION.SDK_INT > 13) 
				{
					urlConnection.setRequestProperty("Connection", "close");
				}
				
				urlConnection.connect();
				
				int connectionCode = urlConnection.getResponseCode();
				
				if(connectionCode == 200)
				{
					if(debugOn)
					{
						Log.d(TAG, "success @ delete Note");
					}
					return "DONE";
				}
				else if(connectionCode == 404)
				{
					Log.e(TAG, "failure @ delete note. note " + urlString.substring(urlString.lastIndexOf('/')) + " does not exist");
					return "404 " + urlString.substring(urlString.lastIndexOf('/') + 1);
				}
				else if(connectionCode == 403)
				{
					connectionError = true;
					Log.e(TAG, "failure @ delete note. permission problem (error code 403)");
					return "ERROR";
				}
				else
				{
					connectionError = true;
					Log.e(TAG, "failure @ delete new Note. response code:" + connectionCode);
					return "ERROR";
				}
			}
			catch(MalformedURLException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "malformed url in UpdateNotesTask:" + e.toString());
				return "ERROR";
			}
			catch(IOException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "ioException in UpdateNotesTask:" + e.toString());
				return "ERROR";
			}
			finally
			{
				if (urlConnection != null)
				{
					urlConnection.disconnect();
				}
			}
		}
		
		protected void onPostExecute(String result)
		{
			if(result.equals("DONE") )
			{
				//everything fine. nothing to do
			}
			else
			{
				//there was a delete-error. 
				if(result.startsWith("404") )
				{
					//note to delete does not exist on server. must be deleted from sql database
					String idToDelete = result.substring("404 ".length() );
					Log.i("DELETETASK", "onPost: delete error: note not found. removing note with id=" + idToDelete + " from sqldatabase");
					
					removeNoteFromSqlDatabase(idToDelete);
				}
				else
				{
					//no connection could be made.
					connectionError = true; //this variable is checked before the sql-database is updated.
					Log.e("DELETENOTES", "onPost: delete error");
				}
			}
		}
	}
		
	//----------------------------------
	//AsyncTask for doing the updating
	//----------------------------------
	private class UpdateNotesTask extends AsyncTask<String, Void, String> 
	{
		protected String doInBackground(String... strings) 
		{
			URL url = null;
			HttpsURLConnection urlConnection = null;
			HttpsURLConnection urlTestConnection = null;
			OutputStream outputStream = null;
			String urlString = strings[0];
			String toPost = strings[1];

			try
			{
				JSONObject json = new JSONObject(toPost);
				url = new URL(urlString);
				
				urlTestConnection = (HttpsURLConnection) url.openConnection();
				
				urlConnection = (HttpsURLConnection) url.openConnection();
				urlConnection.setDoOutput(true);
				urlConnection.setRequestMethod("PUT");
				urlConnection.setUseCaches(false);
				
				String auth = settings.getString(SettingsActivity.PREF_USERNAME, "username") + ":" + settings.getString(SettingsActivity.PREF_PASSWOORD, "password");
				String basicAuth = "Basic " + new String(Base64.encode(auth.getBytes(), Base64.NO_WRAP));
				urlConnection.setRequestProperty("Authorization", basicAuth);
				urlTestConnection.setRequestProperty("Authorization", basicAuth);
				
				urlConnection.setFixedLengthStreamingMode(json.toString().getBytes().length);
				urlConnection.setRequestProperty("Content-Type", "application/json");
				
				if (Build.VERSION.SDK_INT > 13) 
				{
						urlConnection.setRequestProperty("Connection", "close");
						urlTestConnection.setRequestProperty("Connection", "close");
				}

				urlTestConnection.connect();
				
				int testConnectionResponseCode = urlTestConnection.getResponseCode();
				
				if( testConnectionResponseCode == 200)
				{
					if(debugOn)
					{
						Log.d(TAG, "update connection ok, doing the updating");
					}
					urlConnection.connect();
					
					outputStream = new BufferedOutputStream(urlConnection.getOutputStream() );
					outputStream.write(json.toString().getBytes());
					outputStream.flush();
					outputStream.close();
					
					int connectionCode = urlConnection.getResponseCode();
				
					if(connectionCode == 200)
					{
						if(debugOn)
						{
							Log.d(TAG, "success @ update new Note");
						}
						return "DONE";
					}
					else if(connectionCode == 404)
					{
						Log.e(TAG, "failure @ update note. note " + urlString.substring(urlString.lastIndexOf('/')) + " does not exist. ");
						return "404 " + urlString.substring(urlString.lastIndexOf('/') + 1) ;
					}
					else if(connectionCode == 403)
					{
						connectionError = true;
						Log.e(TAG, "failure @ update note. permission problem (error code 403)");
						return "ERROR";
					}				
					else
					{
						connectionError = true;
						Log.e(TAG, "failure @ update new Note. response code:" + connectionCode);
						return "ERROR";
					}
				
				}
				else if(testConnectionResponseCode == 404)
				{
					Log.e(TAG, "failure @ update note. note " + urlString.substring(urlString.lastIndexOf('/')) + " does not exist. ");
					return "404 " + urlString.substring(urlString.lastIndexOf('/') + 1) ;
				}
				else
				{
					connectionError = true;
					Log.e(TAG, "No update connection could be established. Response code: " + testConnectionResponseCode );
					return "ERROR";
				}
			}
			catch(MalformedURLException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "malformed url in UpdateNotesTask:" + e.toString());
				return "ERROR";
			}
			catch(IOException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "ioException in UpdateNotesTask:" + e.toString());
				return "ERROR";
			}
			catch(JSONException jsonE)
			{
				connectionError = true;
				jsonE.printStackTrace();
				Log.e(TAG, "jasonException in UpdateNotesTask:" + jsonE.toString());
				return "ERROR";
			}
			finally
			{
				if (urlConnection != null)
				{
					urlConnection.disconnect();
				}
			}
		}
		
		protected void onPostExecute(String result)
		{
			if(result.equals("DONE") )
			{
				//everything fine. nothing to do.
			}
			else
			{
				//there was an update-error. 
				if(result.startsWith("404") )
				{
					//note to update not found on server. delete the note from database.
					String idToDelete = result.substring("404 ".length() );
					Log.i("UPDATETASK", "onPost: update error: note not found. removing note with id=" + idToDelete + " from sqldatabase");
					
					removeNoteFromSqlDatabase(idToDelete);
				}
				else
				{
					//seems that no connection could be made.
					connectionError = true; //this variable is checked before the sql-database is updated.
					Log.e("UPDATETASK", "onPost: update error");
				}
			}
		}
	}
	
	//----------------------------------
	//AsyncTask for doing the uploading
	//----------------------------------
	private class UploadNotesTask extends AsyncTask<String, Void, Boolean> 
	{
		protected Boolean doInBackground(String... strings) 
		{
			URL url = null;
			HttpsURLConnection urlConnection = null;
			HttpsURLConnection urlTestConnection = null;
			OutputStream outputStream = null;
			String urlString = strings[0];
			String toPost = strings[1];

			try
			{
				JSONObject json = new JSONObject(toPost);
				//Log.d(TAG, "json= " + json.toString() );
				url = new URL(urlString);
				
				urlTestConnection = (HttpsURLConnection) url.openConnection();
				
				urlConnection = (HttpsURLConnection) url.openConnection();
				urlConnection.setDoOutput(true);
				urlConnection.setRequestMethod("POST");
				urlConnection.setUseCaches(false);
				
				String auth = settings.getString(SettingsActivity.PREF_USERNAME, "username") + ":" + settings.getString(SettingsActivity.PREF_PASSWOORD, "password");
				String basicAuth = "Basic " + new String(Base64.encode(auth.getBytes(), Base64.NO_WRAP));
				urlConnection.setRequestProperty("Authorization", basicAuth);
				urlTestConnection.setRequestProperty("Authorization", basicAuth);
				
				if (Build.VERSION.SDK_INT > 13) 
				{
						urlConnection.setRequestProperty("Connection", "close");
						urlTestConnection.setRequestProperty("Connection", "close");
				}
				
				urlConnection.setFixedLengthStreamingMode(json.toString().getBytes().length);
				urlConnection.setRequestProperty("Content-Type", "application/json");

				urlTestConnection.connect();
				
				int testConnectionResponseCode = urlTestConnection.getResponseCode();
				
				if( testConnectionResponseCode == 200)
				{
					if(debugOn)
					{
						Log.d(TAG, "upload connection ok, doing the uploading");
					}
					
					urlConnection.connect();
					
					outputStream = new BufferedOutputStream(urlConnection.getOutputStream() );
					outputStream.write(json.toString().getBytes());
					outputStream.flush();
					outputStream.close();
				
					int connectionCode = urlConnection.getResponseCode();
					
					if(connectionCode == 200)
					{
						if(debugOn)
						{
							Log.d(TAG, "success @ upload new Note");
						}
						return true;
					}
					else if(connectionCode == 404)
					{
						connectionError = true;
						Log.e(TAG, "failure @ upload new note. ");
						return false;
					}
					else if(connectionCode == 403)
					{
						connectionError = true;
						Log.e(TAG, "failure @ upload new note. permission problem (error code 403)");
						return false;
					}
					else
					{
						connectionError = true;
						Log.e(TAG, "failure @ upload new Note. response code:" + connectionCode);
						return false;
					}
				}
				else
				{
					Log.e(TAG, "No upload connection could be established. Response code: " + testConnectionResponseCode );
					return false;
				}
			}
			
			catch(MalformedURLException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "malformed url in UploadNotesTask:" + e.toString());
				return false;
			}
			catch(IOException e)
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, "ioException in UploadNotesTask:" + e.toString());
				return false;
			}
			catch(JSONException jsonE)
			{
				connectionError = true;
				jsonE.printStackTrace();
				Log.e(TAG, "jasonException in UplaodNotesTask:" + jsonE.toString());
				return false;
			}
			finally
			{
				if (urlConnection != null)
				{
					urlConnection.disconnect();
				}
			}
		}
		
		protected void onPostExecute(Boolean result)
		{
			if(result == false)
			{
				//there was an upload-error. seems that no connection could be made.
				connectionError = true; //this variable is checked before the sql-database is updated.
				Log.e("UPLOADTASK", "onPost: upload error");
			}
		}
		
	}
	
	//----------------------------------
	//AsyncTask for doing the downloading
	//----------------------------------
	/**
	 * inner class performing the actual downloading from the web
	 * 
	 */
	private class DownloadNotesTask extends AsyncTask<String, Void, String> 
	{
	    /** The system calls this to perform work in a worker thread and
	      * delivers it the parameters given to AsyncTask.execute() */
	    protected String doInBackground(String... anUrl) {
	    	
	    	StringBuilder stringBuilder = new StringBuilder();
	    	HttpsURLConnection urlConnection = null;
	    	URL url = null;
	    	
			try {
				url = new URL(anUrl[0]);

				if(debugOn)
				{
					Log.d("DOWNLOADTASK", "url:" + url.toString() );
				}
				
				urlConnection = (HttpsURLConnection) url.openConnection();
				urlConnection.setDoInput(true);
				urlConnection.setRequestMethod("GET");
				
				String auth = settings.getString(SettingsActivity.PREF_USERNAME, "username") + ":" + settings.getString(SettingsActivity.PREF_PASSWOORD, "password");
				
				//Log.d("DOWNLOADTASK", "auth=" + auth);
				
				String basicAuth = "Basic " + new String(Base64.encode(auth.getBytes(), Base64.NO_WRAP));
				urlConnection.setRequestProperty("Authorization", basicAuth);
				
				if (Build.VERSION.SDK_INT > 13) 
				{
						urlConnection.setRequestProperty("Connection", "close");
				}
				
				urlConnection.connect();
				int connectionCode = urlConnection.getResponseCode();
				
				if(connectionCode == 200)
				{
					Log.d(TAG, "download connection ok, doing the downloading");
					BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
					
					String line;
					while( (line = reader.readLine() ) != null)
					{
						stringBuilder.append(line);
						//Log.d(TAG, "line:" + line);
					}
				}
				else
				{
					connectionError = true;
					Log.e(TAG, "error @ downloading notes. response code: " + connectionCode);
					return "ERROR connection";
				}
			} 
			catch (MalformedURLException e) 
			{
				connectionError = true;
				e.printStackTrace();
				Log.e(TAG, e.toString());
				
				return "ERROR MalformedURLException";
			}
			catch(FileNotFoundException e)
			{
				connectionError = true;
				e.printStackTrace();				
				Log.e(TAG, e.toString() );
				
				return "ERROR FileNotFoundException";
			}
			catch(SSLHandshakeException e)
			{
				connectionError = true;
				if(debugOn)
				{
					e.printStackTrace();
					Log.e(TAG, e.toString());
				}
				
				return "ERROR SSLHandshakeException";
			}
	    	catch (IOException e) 
	    	{
	    		connectionError = true;
	    		e.printStackTrace();
	    		Log.e(TAG, e.toString() );
	    		
	    		return "ERROR IOException";
			}
			finally
			{
				if (urlConnection != null)
				{
					urlConnection.disconnect();
				}
			}
			
	    	return stringBuilder.toString();
	    }
	    
	    /** The system calls this to perform work in the UI thread and delivers
	      * the result from doInBackground() */
	    protected void onPostExecute(String result) {
	    	if(result.equals("ERROR MalformedURLException") )
	    	{
	    		Toast.makeText(getApplicationContext(), R.string.toast_url_not_correctly_formed, Toast.LENGTH_LONG).show();
	    		setSyncNotInProgress();
	    	}
	    	else if(result.equals("ERROR IOException") )
			{
	    		Toast.makeText(getApplicationContext(), R.string.toast_url_doesnt_exist, Toast.LENGTH_LONG).show();
	    		setSyncNotInProgress();
			}
	    	else if(result.equals("ERROR FileNotFoundException" ) )
	    	{
	    		Toast.makeText(getApplicationContext(), R.string.toast_connection_error + R.string.toast_check_username_password, Toast.LENGTH_LONG).show();
	    		setSyncNotInProgress();
	    	}
	    	else if(result.equals("ERROR SSLHandshakeException") )
	    	{
	    		setSyncNotInProgress();
	    		showSSLAlert();
	    	}
	    	else if(result.equals("ERROR connection"))
	    	{
	    		Toast.makeText(getApplicationContext(), R.string.toast_connection_error, Toast.LENGTH_LONG).show();
	    		setSyncNotInProgress();
	    	}
	    	else
	    	{
	    		//"result" contains a JSON with _all_ notes from owncloud server.
	    		if( ! connectionError) //only update database if upload/update/delete cycle was successful
	    		{
	    			updateDatabase(result);
	    			if(debugOn)
	    			{
	    				Log.d(TAG, "updateDatabase() executed" );
	    			}
	    		}
	    		else
	    		{
	    			Log.e(TAG, "list not updated due to connection error");
	    			Toast.makeText(getApplicationContext(), R.string.toast_connection_error, Toast.LENGTH_LONG).show();
	    			setSyncNotInProgress();
	    		}
	    	}
	    	
	    }
	}
}