package com.sharefaith.thesharefaithapp.base;

import android.app.Activity;
import android.os.AsyncTask;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import com.appideas.base.AiDb;
import com.appideas.base.AiSTr;
import com.sharefaith.thesharefaithapp.R;
import com.sharefaith.thesharefaithapp.models.SFNewsletterModel;
import com.sharefaith.thesharefaithapp.models.SFPostModel;
import com.sharefaith.thesharefaithapp.models.SFSermonModel;
import com.sharefaith.thesharefaithapp.models.SFSermonSeriesModel;
import com.sharefaith.thesharefaithapp.ui.SFSharedUIMethods;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Created by Sharefaith1 on 2014-12-02.
 */
public class SFBackgroundSync
{
	public SFApplication mCaller;
	public AiDb mDb;
	public boolean mHadError = false;
	public int mTotalSize;
	public int mReceivedSize;
	public boolean mIsInitialSync;
	public boolean mMustRefresh;

	public boolean mHaveNewsletters;
	public boolean mHaveSermons;
	public boolean mHavePosts;
	//public boolean mHaveConnect;
	public boolean mFullSync;

	boolean reorderNewsletters = false;
	boolean reorderPlaylists = false;
	boolean reorderSermons = false;
	boolean reorderPosts = false;

	// changes booleans
	boolean themeChanged;
	boolean sectionsChanged;
	boolean mInboxChanged;
	boolean mBibleChanged;
	boolean mCalendarChanged;

	//Url to get the app names.
	private static final String CHURCHLIST_URL = "https://appserve.sharefaith.com/singleApp.php?action=getChurchList";

	//bible vars
	ArrayList<Integer> bibleIds = new ArrayList<>();
	int mCurrentBibleWPId;

	//calendar vars
	ArrayList<Integer> mCalendarIds = new ArrayList<>();

	private boolean isDemo;

	/**
	 * Constructor
	 * @param caller
	 */
	public SFBackgroundSync( SFApplication caller )
	{
		this.mCaller = caller;
		this.mDb = mCaller.getDb();
		this.mTotalSize = 0;
		this.mReceivedSize = 0;
		this.mIsInitialSync = false;
		this.mMustRefresh = false;

		this.mHaveNewsletters = false;
		this.mHaveSermons = false;
		this.mHavePosts = false;
		//this.mHaveConnect = false;
		this.mFullSync = false;

		themeChanged = false;
		sectionsChanged = false;
		mInboxChanged = false;
		mBibleChanged = false;
		isDemo = SFConstants.IS_DEMO;
	}


	public void doInitialSync()
	{
		this.mIsInitialSync = true;
		this.getinitialDownloadSize();
	}

	/**
	 * continue initial sync if stopped without completing
	 * @param downloadSize
	 */
	public void continueInitialSync( int downloadSize )
	{
		this.mTotalSize = downloadSize;
		this.doSync();
	}

	/**
	 *
	 * @param bytes
	 */
	public void updateProgress( int bytes )
	{
		if( this.mTotalSize > 0 && bytes > 0 )
		{
			this.mReceivedSize += bytes;
			int percent = (int) Math.ceil((((double)this.mReceivedSize / (double)this.mTotalSize) * 1000.0));
			//this.mCaller.mSyncFragment.mProgressBar.setProgress( percent );
		}
	}

	public void getinitialDownloadSize()
	{
		SFConfig config = new SFConfig();
		String prefix = SFConstants.UAID_KEY.substring( 0, 4 );
		String suffix = SFConstants.UAID_KEY.substring( 4 );

		if( isDemo )
		{
			prefix = "demo/" + prefix;
		}

		final String imagesAddress = SFConstants.APPSERVE_BASE_URL + "/userfiles/" + prefix + "/" + suffix + "/images/inapp.zip";
		final String dbAddress = SFConstants.APPSERVE_BASE_URL + "/userfiles/" + prefix + "/" + suffix + "/data/com.sharefaith.churchapp.prebaked.sqlite";

		final SFBackgroundSync caller = this;

		Thread t = new Thread(
				new Runnable()
				{
					int totalSize = 0;
					public void run()
					{

						try
						{
							URL imagesUrl = new URL( imagesAddress );
							URLConnection imagesConnection = imagesUrl.openConnection();
							imagesConnection.connect();
							totalSize += imagesConnection.getContentLength();

							URL dbUrl = new URL( dbAddress );
							URLConnection dbConnection = dbUrl.openConnection();
							dbConnection.connect();
							totalSize += dbConnection.getContentLength();
						}
						catch( Exception e )
						{
							totalSize  = 0;
						}

						caller.continueInitialSync( totalSize );
					}
				}

		);
		t.start();
	}

	public void addToTotalSize( int size )
	{
		this.mTotalSize += size;
	}

	/**
	 * Starts the background sync
	 */
	public void doSync()
	{
		SFConfig config = new SFConfig();

		// First-time sync. Run through each step of the process if each one passes
		if( config.mLastSync < 1 )
		{
			if( this.getAppImages() )
			{
				if( this.syncInitialAppData() )
				{
					this.connectAppImages();
					this.connectMoreImages();

					SFAppData.saveSyncTime();
					config.mLastSync++;

					Thread t = new Thread(
							new Runnable()
							{
								public void run()
								{
									SFBackgroundSync sync = new SFBackgroundSync( SFApplication.getInstance() );
									sync.firstTimeLoadAddHashes();
								}
							}
					);
					t.start();
					final Activity currentActivity = SFApplication.getInstance().getCurrentActivity();
					currentActivity.runOnUiThread(
							new Runnable()
							{
								@Override
								public void run()
								{
									SFApplication application = SFApplication.getInstance();
									SFAppData appData = new SFAppData();
									String sectionTag = appData.getFirstSectionTag();
									application.currentSectionPosition = 0;
									SFSharedUIMethods.swapToViewWithTag( sectionTag,0);
									application.getCurrentActivity().finish();
								}
							}
					);
					return;
				}
			}
		}
		else
		{
			downloadHashes();
			downloadChurchNames();
			//testing
			/*String sql =
					"DELETE FROM section_content_meta WHERE meta_key = 'orderditems'";
			SFAppData appdata = new SFAppData();
			appdata.debugQuery( sql );*/

			compareHashes();
		}
	}

	/**
	 * update the inbox
	 */
	public void updateInbox()
	{
		this.mInboxChanged = true;
		DownloadTask inboxTask = new DownloadTask( mCaller, true );
		inboxTask.getInbox();
	}

	/**
	 * get inbox items to be synced first
	 * @param externalID
     */
	public void processNotification( int externalID )
	{
		//Log.d("mTag","Hit processNotification with external id = " +externalID);
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add( externalID );
		DownloadTask task = new DownloadTask( this.mCaller, this.mFullSync );
		task.getObjects( list );
		//closeDialog( true );
	}

	/**
	 * Stop the refresh arrow animation
	 */
	private void stopRefreshAnimation()
	{
		final Activity currentActivity = SFApplication.getInstance().getCurrentActivity();
		currentActivity.runOnUiThread(
				new Runnable()
				{
					@Override
					public void run()
					{
						SFSharedUIMethods.stopAnimateRefresh();
					}
				}
		);
	}

	/**
	 * download church names
	 */
	public static void downloadChurchNames()
	{
		String address = CHURCHLIST_URL;

		HttpURLConnection connection = null;
		FileOutputStream fos = null;
		InputStream is = null;
		SFConfig mSFConfig = new SFConfig();
		try
		{
			URL url = new URL( address );
			BufferedReader in = new BufferedReader( new InputStreamReader( url.openStream() ) );
			String line;

			//set up to download file
			url = new URL( address );
			connection = (HttpURLConnection) url.openConnection();
			connection.connect();

			// expect HTTP 200 OK, so we don't mistakenly save error report
			// instead of the file
			if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
			{
				String message = "Could not connect to server";
				//SFMainActivity.makeToast( message );
				return;
			}

			// this will be useful to display download percentage
			// might be -1: server did not report the length
			int fileLength = connection.getContentLength();

			// download the file
			is = connection.getInputStream();

			//Check if file exists
			File file = new File( mSFConfig.mCustomAssetPath + "churchNames.json");
			if(file.exists()) {
				file.delete();
			}

			fos = new FileOutputStream(mSFConfig.mCustomAssetPath + "churchNames.json");
			byte data[] = new byte[1024];
			long total = 0;
			int count;
			while ((count = is.read( data )) != -1)
			{
				total += count;
				fos.write( data, 0, count );
			}
		} catch (Exception e)
		{
			String message = e.getLocalizedMessage();
		} finally
		{
			try
			{
				if (fos != null)
					fos.close();
				if (is != null)
					is.close();
			} catch (IOException ignored)
			{
			}

			if (connection != null)
				connection.disconnect();
		}

	}

	/**
	 * download the hash table
	 */
	public void downloadHashes()
	{
		//syncVersion not used
		//int syncVersion = (this.mFullSync) ? SFConstants.SYNC_VERSION : 0;

		// if we made it here, the app has synced at least once or there was an error along the way

		//String siteKey = this.mCaller.getPreference( "sitekey" );
		String uaid = SFConstants.UAID_KEY;
		String address = SFApplication.getInstance().getResources().getString( R.string.appserve_url ) + "?action=getHashes&i=" + uaid;

		//this will need to be fixed for demo
		//if( SFConstants.IS_DEMO )
		//{
		//	uaid = (this.mIsInitialSync) ? SFConstants.UAID_KEY : siteKey;
		//	address = SFConstants.DEMO_APPSERVE_URL + "?action=getSyncObjects&i=" + uaid + "&t=" + config.mLastSync + "&v=" + syncVersion;
		//}

		HttpURLConnection connection = null;
		FileOutputStream fos = null;
		InputStream is = null;
		SFConfig mSFConfig = new SFConfig();
		try
		{
			URL url = new URL( address );
			BufferedReader in = new BufferedReader( new InputStreamReader( url.openStream() ) );
			String line;
			address = "";


			while ((line = in.readLine()) != null)
			{
				if (line.equalsIgnoreCase( "bye" ))
				{
					Log.e( "SOCKET ERROR", "Socket said goodbye" );
				}
				//get lines
				address += line;
			}
			in.close();

			//set up to download file
			url = new URL( address );
			connection = (HttpURLConnection) url.openConnection();
			connection.connect();

			// expect HTTP 200 OK, so we don't mistakenly save error report
			// instead of the file
			if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
			{
				this.mHadError = true;
				String message = "Could not connect to server";
				//SFMainActivity.makeToast( message );
				return;
			}

			// this will be useful to display download percentage
			// might be -1: server did not report the length
			int fileLength = connection.getContentLength();

			// download the file
			is = connection.getInputStream();
			fos = new FileOutputStream( mCaller.getApplicationContext().getFilesDir() + mSFConfig.mDbUpdatesPath + "external_db.sqlite" );

			byte data[] = new byte[1024];
			long total = 0;
			int count;
			while ((count = is.read( data )) != -1)
			{
				total += count;
				fos.write( data, 0, count );
			}


		} catch (Exception e)
		{
			this.mHadError = true;
			String message = mCaller.getString( R.string.sync_error ) + e.getLocalizedMessage();
			mCaller.makeToast( message );
		} finally
		{
			try
			{
				if (fos != null)
					fos.close();
				if (is != null)
					is.close();
			} catch (IOException ignored)
			{
			}

			if (connection != null)
				connection.disconnect();
		}

	}

	/**
	 * hashes all local items and then syncs
	 */
	public void firstTimeLoadAddHashes()
	{
		refreshAllLocalHashes();
		doSync();
	}

	/**
	 * re-hash all local items
	 */
	public void refreshAllLocalHashes()
	{
		String sql = "DELETE FROM sync_objects";
		this.mDb.noReturnQuery( sql );

		this.mCaller.isHashTableEmpty = true;

		addHash( 0, "theme" );
		addHash( 0, "section" );


		sql =
				"SELECT id FROM ondevice_sections " +
						" WHERE sf_tag = 'sermons'";
		String[][] sermonSections = this.mDb.query( sql );

		for (int i = 0; i < sermonSections.length; i++)
		{
			sql =
					" SELECT playlist_section_content_id FROM playlist_to_section " +
							" WHERE ondevice_section_id = " + AiSTr.denullifyInt( sermonSections[i][0] );
			String[][] playlists = this.mDb.query( sql );

			for (int j = 0; j < playlists.length; j++)
			{
				SFSermonSeriesModel playlist = new SFSermonSeriesModel( AiSTr.denullifyInt( playlists[j][0] ) );
				playlist.saveHashValue();

				sql =
						" SELECT sermon_section_content_id FROM sermon_to_playlist " +
								" WHERE playlist_section_content_id = " + AiSTr.denullifyInt( playlists[j][0] );
				String[][] sermons = this.mDb.query( sql );
				for (int k = 0; k < sermons.length; k++)
				{
					SFSermonModel sermon = new SFSermonModel( AiSTr.denullifyInt( sermons[k][0] ) );
					sermon.saveHashValue();
				}
			}
		}

		sql =
				"SELECT id FROM ondevice_sections " +
						" WHERE sf_tag = 'posts' OR sf_tag = 'newsletter'";
		String[][] postSection = this.mDb.query( sql );

		for (int i = 0; i < postSection.length; i++)
		{
			sql =
					" SELECT post_section_content_id FROM post_to_section " +
							" WHERE ondevice_section_id = " + AiSTr.denullifyInt( postSection[i][0] );
			String[][] posts = this.mDb.query( sql );

			for (int j = 0; j < posts.length; j++)
			{
				SFPostModel post = new SFPostModel( AiSTr.denullifyInt( posts[j][0] ) );
				post.saveHashValue();
			}
		}
		this.mCaller.isHashTableEmpty = false;
	}

	/**
	* adds or replaces hash in the database
	*
	 */
	public void addHash(int externalId, String type)
	{
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();
		String hash = "";
		if(type.equals( "image" ))
		{
			return;
		}
		else
		{
			hash = appData.getStringToHash( externalId, type );

			//Log.d( "mTag", "string to hash = " + hash );

			hash = util.md5( hash );

			//Log.d( "mTag", "string after hash = " + hash );

		}
		appData.addHashData( externalId,hash,type );
	}

	/**
	 * update hash of a link
	 * @param externalId
	 * @param primaryId
	 * @param type
     */
	public void hashLink(int externalId, int primaryId, String type )
	{
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();
		String hash = "";

		hash = appData.getLinkStringToHash( primaryId );
		//Log.d("mTag", "string to hash = " + hash );
		hash = util.md5( hash );
		//Log.d( "mTag", "string after hash = " + hash );

		appData.addHashData( externalId,hash,type );
	}

	/**
	 * update the hash of a connect section
	 * @param externalId
	 * @param primaryId
	 * @param type
     */
	public void hashConnect(int externalId, int primaryId, String type )
	{
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();
		String hash = "";

		hash = appData.getConnectStringToHash( primaryId );
		//Log.d("mTag", "string to hash = " + hash );
		hash = util.md5( hash );
		//Log.d( "mTag", "string after hash = " + hash );

		appData.addHashData( externalId,hash,type );
	}

	/**
	 * update the hash of a location
	 * @param externalId
	 * @param primaryId
	 * @param type
     */
	public void hashLocation(int externalId, int primaryId, String type )
	{
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();
		String hash = "";

		hash = appData.getLocationStringToHash( primaryId );
		//Log.d("mTag", "string to hash = " + hash );
		hash = util.md5( hash );
		//Log.d( "mTag", "string after hash = " + hash );

		appData.addHashData( externalId,hash,type );
	}

	/**
	 * update the hash of a Stream section
	 * @param externalId
	 * @param primaryId
	 * @param type
     */
	public void hashStream(int externalId, int primaryId, String type )
	{
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();
		String hash = "";

		hash = appData.getStreamStringToHash( primaryId );
		//Log.d("mTag", "string to hash = " + hash );
		hash = util.md5( hash );
		//Log.d( "mTag", "string after hash = " + hash );

		appData.addHashData( externalId,hash,type );
	}

	/**
	 * compare local hashes to the external hash file to find out what needs to be updated
	 */
	public void compareHashes()
	{
		//testing
		//ArrayList<Integer> objectIdsTemp = new ArrayList<Integer>();
		//DownloadTask taskTemp = new DownloadTask( mCaller, true );
		//taskTemp.getObjects( objectIdsTemp );


		//Log.d("mtag", "HIT COMPARE HASHES");
		String filepath = mCaller.getFilesDir() + "/appfiles/dbupdates/external_db.sqlite";
		File file = new File(filepath);
		if(file.exists())
		{
			SFConfig mSFConfig = new SFConfig();
			String sql = "";
			ArrayList<Integer> objectIds = new ArrayList<Integer>();

			boolean hasUpdate = false;
			boolean objectRemoved = false;

			sql = "SELECT COUNT(*) AS count " +
					"FROM sync_objects";
			String[][] result = mDb.query( sql );
			int count = AiSTr.denullifyInt( result[0][0] );
			if( count <1)
			{
				refreshAllLocalHashes();
			}

			AiDb hashDb = new AiDb( mCaller, mSFConfig.mDbUpdatesPath, "external_db.sqlite" );
			sql = "SELECT syncObjectId, isActive, v2HashValue, objectType FROM SyncObjects";
			String[][] hashResult = hashDb.query( sql );

			if (hashResult.length == 0)
			{
				hashDb.close();
				return;
			}


			for (int i = 0; i < hashResult.length; i++)
			{
				int results = compareHash( hashResult[i] );
				if (results > 0)
				{
					//Log.d("mTag","adding object " + result );
					hasUpdate = true;
					objectIds.add( results );
				} else if (results == -2)
				{
					//Log.d("mTag","object removed");
					objectRemoved = true;
				} else if (results == -3)
				{
					//Log.d("mTag","update forced");
					hasUpdate = true;
				}
			}

			//testing
			//hasUpdate = true;
			//objectIds.add( 51123 );
			//Log.d( "mtag", "object ids: " + objectIds.toString() );

			if (hasUpdate)
			{
				DownloadTask task = new DownloadTask( mCaller, true );
				task.getObjects( objectIds );
			} else if (objectRemoved)
			{
				checkForRefresh();
				finishSync();
				//stopRefreshAnimation();
				//mCaller.hideProgress();

				//testing
				//endTime = System.nanoTime();
				//Log.d( "mTag", "END TIME************************* " + endTime + ": difference of  " + ((endTime - startTime)/ 1000000 ) + " milliseconds");
			}
			else
			{
				finishSync();
			}
			hashDb.close();

			// TODO: 12/17/15  uncomment commented for testing
			//testing
			file.delete();

		}
	}

	/**
	 * compare the local hash of an item to the hash in the external table
	 * @param object
	 * @return
     */
	public int compareHash( String[] object )
	{

		SFAppData appData = new SFAppData();
		AiDb primaryDb = mDb;
		//SFUtil util = new SFUtil();
		//int result = -1;
		int objectId = Integer.valueOf( object[0] );
		int active = Integer.valueOf( object[1] );
		String hash = object[2];
		String type = object[3];
		String[][] oldHashResult = null;
		String[][] results;

		if(objectId==56||objectId==57||objectId==58)
		{
			return -1;
		}

		if( type.equals( "theme" )|| type.equals( "sections" ) || type.equals( "inbox" ) )
		{
			String sql = "SELECT sync_object_id, hash_value, object_type " +
					"FROM sync_objects " +
					"WHERE object_type = '" + type + "'"
					;
			results = primaryDb.query( sql );
			for( int i = 0; i < results.length; i++)
			{
				oldHashResult = results;
			}
		}
		else
		{

			String sql = "SELECT sync_object_id, hash_value, object_type " +
					"FROM sync_objects " +
					"WHERE sync_object_id = " + objectId + " AND " +
					"object_type = '" + type + "'";
			results = primaryDb.query( sql );
			for( int i = 0; i < results.length; i++)
			{
				oldHashResult = results;
			}
		}

		//Log.d( "mtag", "type = " + type);

		if( results.length < 1 )
		{
			if( active == 1 )
			{
				if(type.equals( "bible" ))
				{
					bibleIds.add( objectId );
					mBibleChanged = true;
					//Log.d("mTag","bible changed = " + mBibleChanged );
					return -3;
				}
				if(type.equals( "calendar" ))
				{
					mCalendarIds.add( objectId );
					mCalendarChanged = true;
					//Log.d("mTag","add calendar");
					return -3;
				}
				return objectId;
			}
			else
			{
				//Log.d("mTag", "DID NOT FIND OBJECT BUT NOT ACTIVE RETURN");
				return -1;
			}
		}
		else if(oldHashResult != null)
		{
			//Log.d("mTag", "FOND OBJECT DO LOGIC FOR ID " + objectId);

			int oldObjectId = Integer.valueOf( oldHashResult[0][0] );
			String oldHash = oldHashResult[0][1];
			String oldType = oldHashResult[0][2];

			if(type.equals( "theme" ))
			{
				//Log.d("mTag","compare hashes theme");
				if( hash.equals( oldHash ))
				{
					//Log.d("mTag","theme match");
					return -1;
				}
				//Log.d("mTag","Hit theme changed");
				themeChanged = true;

				return -3;
			}
			else if(type.equals( "sections" ))
			{
				//Log.d("mTag","compare hashes section");
				if( hash.equals( oldHash ))
				{
					//Log.d("mTag","sections match");
					return -1;
				}
				//Log.d("mTag","Hit sections changed");
				sectionsChanged = true;

				return -3;
			}
			else if (type.equals( "inbox" ))
			{
				//Log.d("mTag","Hit compare inbox");

				if( hash.equals( oldHash ))
				{
					return -1;
				}

				mInboxChanged = true;
				//Log.d("mTag","inboxChanged = " + mInboxChanged );
				return -3;
			}

			else if (type.equals( "bible" ))
			{
				//Log.d("mTag","Hit compare bible");
				//Log.d("mTag","old hash: " + oldHash + ", new hash = " + hash);

				if( hash.equals( oldHash ))
				{
					return -1;
				}
				bibleIds.add( objectId );
				mBibleChanged = true;
				//Log.d("mTag","bible changed = " + mBibleChanged );
				return -3;
			}
			else if (type.equals("calendar"))
			{
				//Log.d("mTag","Hit compare calendar");
				//Log.d("mTag","old hash: " + oldHash + ", new hash = " + hash);
				if( hash.equals( oldHash ) )
				{
					//Log.d("mTag","Hit calendar hash match");
					return -1;
				}
				//Log.d("mTag","Hit calendar hash NO match");
				mCalendarIds.add( objectId );
				mCalendarChanged = true;
				return -3;
			}

			else if (active == 0)
			{

				//Log.d("mTag","hit remove with id = " + objectId);
				//delete from app
				if (type.equals( "newsletter" ))
				{
					SFNewsletterModel newsModel = SFNewsletterModel.objectFromExternalId( objectId );
					reorderNewsletters = true;
					appData.removeHashData(objectId);
					newsModel.remove();
					return -2;

				}
				else if (type.equals( "post" ))
				{
					//Log.d("mTag","hit post remove on: " + objectId);
					SFPostModel postModel = SFPostModel.objectFromExternalId( objectId );
					reorderPosts = true;
					appData.removeHashData(objectId);
					postModel.remove();
					return -2;
				}
				else if (type.equals( "playlist" ))
				{
					SFSermonSeriesModel seriesModel = SFSermonSeriesModel.objectFromExternalId( objectId );
					reorderPlaylists = true;
					appData.removeHashData(objectId);
					seriesModel.remove();

					return -2;
				}
				else if (type.equals( "sermon" ))
				{
					//Log.d("mTag","Removing sermon ");
					appData.removeHashData(objectId);
					SFSermonModel.removeSermonFromExternalID( objectId );
					reorderPlaylists = true;
					return -2;
				}
				else if( type.equals( "bible" ) || type.equals( "calendar" ) || type.equals( "donate" ) || type.equals( "stream" ) )
				{
					appData.removeHashData( objectId );
				}
			}
			else
			{
				if (hash.equals( oldHash ))
				{
					return -1;
				}
				else
				{
					return objectId;
				}
			}
		}
		return -4;
	}

	/**
	 * Downloads the images for the app
	 * @return
     */
	public boolean getAppImages()
	{
		SFConfig config = new SFConfig();
		String prefix = SFConstants.UAID_KEY.substring( 0, 4 );
		String suffix = SFConstants.UAID_KEY.substring( 4 );

		if (isDemo)
		{
			prefix = "demo/" + prefix;
		}

		String imagesUrl = SFConstants.APPSERVE_BASE_URL + "/userfiles/" + prefix + "/" + suffix + "/images/inapp.zip";
		//Log.d("mTag","imagesURL = " + SFConstants.APPSERVE_BASE_URL + "/userfiles/" + prefix + "/" + suffix + "/images/inapp.zip");

		boolean returnValue;

		SFCustomDownload dl = new SFCustomDownload( this );
		String imagesFile = "";
		try
		{
			//this.mCaller.mSyncFragment.updateStatus( this.mCaller.getString( R.string.downloading_media ) );
			imagesFile = dl.downloadFile( imagesUrl );
		} catch (Exception e)
		{
			// If we got an exception, we just record empty file
			return false;
		}

		returnValue = SFUtil.unpackZip( config.mCustomAssetPath, imagesFile );

		String filePath = config.mCustomAssetPath + imagesFile;
		File file = new File( filePath );
		file.delete();


		return returnValue;
	}

	/**
	 * intitiate the local database and populate with the initial app data
	 * @return
     */
	public boolean syncInitialAppData()
	{
		SFConfig config = new SFConfig();
		String prefix = SFConstants.UAID_KEY.substring( 0, 4 );
		String suffix = SFConstants.UAID_KEY.substring( 4 );

		if (isDemo)
		{
			prefix = "demo/" + prefix;
		}

		String dbUrl = SFConstants.APPSERVE_BASE_URL + "/userfiles/" + prefix + "/" + suffix + "/data/com.sharefaith.churchapp.prebaked.sqlite";

		boolean returnValue;

		SFCustomDownload dl = new SFCustomDownload( this );
		String dbFile = "";
		try
		{
			//this.mCaller.mSyncFragment.updateStatus( this.mCaller.getString( R.string.posts_and_content ) );
			dbFile = dl.downloadFile( dbUrl );
		} catch (Exception e)
		{
			// If we got an exception, we just record empty file
			return false;
		}

		returnValue = SFUtil.unpackZip( config.mCustomAssetPath, dbFile );

		String dbFilePath = config.mCustomAssetPath + dbFile;

		String[] tables = {"config_lookup", "ondevice_sections", "section_content", "section_content_meta","bible_access_history",
				"bible_books", "bible_version_to_book", "bible_versions", "playlist_to_section",
				"post_to_section", "sermon_to_playlist","ondevice_section_meta"};
		String[] fields = {
				"lookup_key, lookup_value",
				"id, sf_tag, display_label, ui_location, position_index, last_update, external_id, titles_enabled, layout, parent_id",
				"id, ondevice_section_id, parent_id, position_index, content, last_update",
				"section_content_id, meta_key, meta_value",
				"id, path, access_time",
				"id, abbreviation, full_name",
				"version_id, book_id, last_chapter, book_order",
				"id, abbreviation, full_name, copyright, sync_version, language, api_provider, api_key, path, is_downloaded, access_time",
				"playlist_section_content_id, ondevice_section_id",
				"post_section_content_id, ondevice_section_id",
				"sermon_section_content_id, playlist_section_content_id",
				"ondevice_section_data_id, meta_key, meta_value"
		};

		String[] queries = new String[(tables.length * 2)];

		int queryCount = 0;
		for (int i = 0; i < tables.length; i++)
		{
			String table = tables[i];
			String field = fields[i];

			queries[queryCount++] = "DELETE FROM " + table;
			queries[queryCount++] = "INSERT INTO " + table + " ( " + field + " ) SELECT " + field + " FROM attachedDb." + table;
		}

		this.mDb.queriesFromAttached( queries, dbFilePath );

		File file = new File( dbFilePath );
		file.delete();
/*
		// translate URLs from demo site content to the user's actual site
		if (SFConstants.IS_DEMO)
		{
			// we have to assume that we've got demo information populated into preferences
			String sql =
					"UPDATE	section_content_meta " +
							"SET	meta_value = REPLACE( meta_value, 'templateapp', '" + this.mCaller.getPreference( "sitekey" ) + "' ) " +
							"WHERE	meta_key IN ( 'thumbnailorigin', 'audiourl' ) ";
			this.mDb.noReturnQuery( sql );

			sql =
					"UPDATE	section_content_meta " +
							"SET	meta_value = REPLACE( meta_value, 'sites/16932/2', 'sites/" + this.mCaller.getPreference( "siteid" ) + "/2' ) " +
							"WHERE	meta_key IN ( 'thumbnailorigin', 'audiourl' ) ";
			this.mDb.noReturnQuery( sql );
		}
*/
		return returnValue;
	}

	/**
	 * connect the app images with the items they are for
	 */
	public void connectAppImages()
	{
		//this.mCaller.mSyncFragment.updateStatus( this.mCaller.getString( R.string.progress_finishing ) );
		String sql =
				"SELECT		id " +
						"FROM		section_content " +
						"WHERE		ondevice_section_id IN " +
						"				(SELECT id FROM ondevice_sections WHERE sf_tag IN ( 'posts', 'newsletter', 'sermons' )) AND " +
						"			parent_id = 0";
		String[][] result = this.mDb.query( sql );
		Integer[] contentIds = new Integer[result.length];
		for (int i = 0; i < result.length; i++)
		{
			contentIds[i] = AiSTr.denullifyInt( result[i][0] );
		}

		for (int i = 0; i < contentIds.length; i++)
		{
			Integer id = contentIds[i];

			sql =
					"SELECT		meta_value " +
							"FROM		section_content_meta " +
							"WHERE		section_content_id = " + id + " AND  " +
							"			meta_key = 'thumbnailorigin' AND " +
							"			meta_value != ''";
			String[][] contentResult = this.mDb.query( sql );
			for (int j = 0; j < contentResult.length; j++)
			{
				String url = AiSTr.denullifyString( contentResult[j][0] );

				SFCustomDownload dl = new SFCustomDownload();
				String savedFile = "";
				try
				{
					savedFile = dl.downloadFile( url );
				} catch (Exception e)
				{
					// If we got an exception, we just record empty file
					Log.e( "PROBLEM", "Something happened trying to get this: " + url );
				}

				String innerSql =
						"UPDATE		section_content_meta " +
								"SET		meta_value = '" + savedFile + "' " +
								"WHERE		section_content_id = " + id + " AND " +
								"			meta_key = 'thumbnail'";
				this.mDb.noReturnQuery( innerSql );
			}
		}
	}

	/**
	 * connect the app images for the more section
	 */
	public void connectMoreImages()
	{
		//this.mCaller.mSyncFragment.updateStatus( this.mCaller.getString( R.string.progress_finishing ) );
		String sql =
				"SELECT external_id " +
						" FROM ondevice_sections " +
						" WHERE sf_tag = 'more'";
		String[][] result = this.mDb.query( sql );
		for(int k = 0; k < result.length; k++)
		{
			int moreId = AiSTr.denullifyInt( result[0][0] );

			sql =
					"SELECT		id " +
							" FROM		ondevice_sections " +
							" WHERE		parent_id = " + moreId;
			result = this.mDb.query( sql );
			Integer[] contentIds = new Integer[result.length];
			for (int i = 0; i < result.length; i++)
			{
				contentIds[i] = AiSTr.denullifyInt( result[i][0] );
			}

			for (int i = 0; i < contentIds.length; i++)
			{
				Integer id = contentIds[i];

				sql =
						"SELECT		meta_value " +
								"FROM		ondevice_section_meta " +
								"WHERE		ondevice_section_data_id = " + id + " AND  " +
								"			meta_key = 'thumbnailorigin' AND " +
								"			meta_value != ''";
				String[][] contentResult = this.mDb.query( sql );
				for (int j = 0; j < contentResult.length; j++)
				{
					String url = AiSTr.denullifyString( contentResult[j][0] );

					SFCustomDownload dl = new SFCustomDownload();
					String savedFile = "";
					try
					{
						savedFile = dl.downloadFile( url );
					} catch (Exception e)
					{
						// If we got an exception, we just record empty file
						Log.e( "PROBLEM", "Something happened trying to get this: " + url );
					}

					String innerSql =
							"UPDATE		ondevice_section_meta " +
									"SET		meta_value = '" + savedFile + "' " +
									"WHERE		ondevice_section_data_id = " + id + " AND " +
									"			meta_key = 'thumbnail'";
					this.mDb.noReturnQuery( innerSql );

				}
			}
		}
	}

	// Shows a "You must refresh now" notice
	public void showMustRefresh()
	{
		if(mCaller.isInForground)
		{
			SFConfig config = new SFConfig();
			LayoutInflater layoutInflater = (LayoutInflater) this.mCaller
					.getSystemService( this.mCaller.LAYOUT_INFLATER_SERVICE );
			View popupView = layoutInflater.inflate( R.layout.just_updated_popup, null );

			TextView showTextView = (TextView) popupView.findViewById( R.id.mustRefreshTextView );
			if (showTextView != null)
			{
				//showTextView.setTypeface( SFTypeFaceSpan.getAssetTypeface( mCaller.getResources().getString( R.string.sf_font_regular ) ) );
				//showTextView.setTextSize( mCaller.getResources().getDimension( R.dimen.sf_must_refresh_font_size ) );
				showTextView.setText( this.mCaller.getString( R.string.must_refresh ) );
			}
/*
			PopupWindow popupWindow = new PopupWindow( popupView,
					(int) SFUtil.dp2px( 370 ),
					(int) SFUtil.dp2px( 200 ) );
			popupWindow.setBackgroundDrawable( new BitmapDrawable() );
			popupWindow.setOutsideTouchable( true );

			final PopupWindow popup = popupWindow;
			final SFBackgroundSync popupCaller = this;

			popupWindow.setOnDismissListener(
					new PopupWindow.OnDismissListener()
					{
						@Override
						public void onDismiss()
						{
							//popupCaller.popupAction( popup );
						}
					}
			);

			popupWindow.setTouchInterceptor(
					new View.OnTouchListener()
					{
						@Override
						public boolean onTouch( View view, MotionEvent motionEvent )
						{
							//popupCaller.popupAction( popup );
							return false;
						}
					}

			);

			try
			{
				//popupWindow.showAtLocation(
					//	this.mCaller.getContainerForTag( this.mCaller.mCurrentViewTag ),
					//	Gravity.CENTER, 0, 0 );
			}
			catch (Exception e)
			{
				Log.e( "mTag", "ERROR: unable to open refresh popup " );
			}*/
		}
	}
/*
	public void popupAction( PopupWindow popup )
	{
		// second level view tags have to be manually removed form view prior to reloading the interface
		String[] viewTags = {"postdetail", "newsletterdetail", "downloads", "sermondetail", "watchsermon", "sermonnotes"};

		if (Arrays.asList( viewTags ).contains( this.mCaller.mCurrentViewTag ))
		{
			this.mCaller.closeMenu();
			LinearLayout currentView = this.mCaller.getContainerForTag( this.mCaller.mCurrentViewTag );
			currentView.setVisibility( View.INVISIBLE );
		}

		//Log.d( "mTag", "current view = " + currentView );
		if( currentView.equals( "sermondetail" ) && this.mCaller.mSeriesListAdapter != null )
		{
			this.mCaller.mSeriesListAdapter.notifyDataSetInvalidated();
		}
		this.mCaller.reloadInterface();
		this.mCaller.swapToViewWithTag( currentView );


		if (popup != null)
		{
			popup.dismiss();
		}
	}
*/
		/*
	public void processNotification( int externalID )
	{
		//Log.d("mTag","Hit processNotification with external id = " +externalID);
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add( externalID );
		DownloadTask task = new DownloadTask( this.mCaller, this.mFullSync );
		task.getObjects( list );
		closeDialog( true );
	}
	*/

	public void closeDialog( boolean reloadInterface )
	{
		/*
		//Log.d("mTag","inForground = " + SFMainActivity.instance.inForground);
		if(mCaller.isInForground)
		{
			final SFApplication application = this.mCaller;
			final boolean doReload = reloadInterface;
			final boolean hadError = this.mHadError;

			final SFBackgroundSync syncCaller = this;

			application.getCurrentActivity().runOnUiThread(
					new Runnable()
					{
						@Override
						public void run()
						{
							if (!hadError)
							{
								SFAppData.saveSyncTime();
							}


							//application.hideProgress();
							application.isSyncing = false;

							if (mainActivity.mFinishAction != null && mainActivity.mFinishAction.length() > 0 && mainActivity.mFinishActionId > 0)
							{
								if (doReload)
								{
									syncCaller.popupAction( null );
									//mainActivity.reloadInterface();
								}

								if (mainActivity.mFinishAction.equals( "gotopost" ))
								{
									mainActivity.swapToViewWithTag( "posts" );
								} else if (mainActivity.mFinishAction.equals( "gotonewsletter" ))
								{
									mainActivity.swapToViewWithTag( "newsletter" );
								} else if (mainActivity.mFinishAction.equals( "gotosermon" ))
								{
									mainActivity.swapToViewWithTag( "sermons" );
								}

							} // if( mainActivity.mFinishAction != null && mainActivity.mFinishAction.length() > 0 && mainActivity.mFinishActionId > 0 )
							else if (syncCaller.mMustRefresh)
							{
								currentView = mainActivity.mCurrentViewTag;
								//Log.d("mTag","currentView from closeDialog = " + currentView);
								syncCaller.showMustRefresh();
							} else
							{
								boolean forwardReload = false;


								if ((mainActivity.mCurrentViewTag.equals( "sermons" ) || mainActivity.mCurrentViewTag.equals( "downloads" )) &&
										syncCaller.mHaveSermons)
								{
									forwardReload = true;
								}
								if (mainActivity.mCurrentViewTag.equals( "posts" ) && syncCaller.mHavePosts)
								{
									forwardReload = true;
								}
								if (mainActivity.mCurrentViewTag.equals( "newsletter" ) && syncCaller.mHaveNewsletters)
								{
									forwardReload = true;
								}
								if (mainActivity.mCurrentViewTag.equals( "contact" ) && syncCaller.mHaveConnect)
								{
									forwardReload = true;
								}

								if (!forwardReload)
								{
									if (syncCaller.mHaveNewsletters)
									{
										mainActivity.layoutNewsletter();
									}
									if (syncCaller.mHavePosts)
									{
										mainActivity.layoutPost();
									}
									if (syncCaller.mHaveConnect)
									{
										mainActivity.layoutConnect();
									}
									if (syncCaller.mHaveSermons)
									{
										mainActivity.layoutSermon();
									}
								} else
								{
									currentView = mainActivity.mCurrentViewTag;
									//Log.d("mTag","currentView from closeDialog = " + currentView);
									syncCaller.showMustRefresh();
								}

							}

						} // public void run()
					} // new Runnable()
			); // runOnUiThread()
		}*/
	}


	/**
	 * process the objects that need to be updated
	 * @param objects
     */
	public void processObjects( ArrayList<HashMap<String,String>> objects )
	{
		//Log.d("mTag","HIT processObjects");
		SFAppData appData = new SFAppData();
		SFUtil util = new SFUtil();

		SFConfig config = new SFConfig();

		if(sectionsChanged)
		{
			for (int i = 0; i < objects.size(); i++)
			{
				HashMap<String, String> data = objects.get( i );

				String type = data.get( "type" );

				if (type.equals( "section" ))
				{
					//Log.d("mTag","Hit process objects type section data = " + data.toString());
					this.mMustRefresh = true;
					//type = "sections";
					appData.saveSectionsFromHashMap( data );
					appData.checkPlaylists();
					addHash( 0, type );
				}
			}
		}

		for (int i = 0; i < objects.size(); i++)
		{
			try
			{

				HashMap<String, String> data = objects.get( i );

				String type = data.get( "type" );
				int objectId = Integer.valueOf( data.get( "id" ) );
				int active = Integer.valueOf( data.get( "active" ) );

				//Log.d( "mTag","processing object " + objectId  );

				// first-time demo needs to change its externalId of some content
				if (isDemo && !config.demoIdsRearranged)
				{
					if (data.get( "secondaryId" ).equals( "Hebrews" ))
					{
						SFAppData.changeExternalId( "128", data.get( "id" ) );
					} else if (data.get( "secondaryId" ).equals( "Psalms" ))
					{
						SFAppData.changeExternalId( "129", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2778" ))
					{
						SFAppData.changeExternalId( "130", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2775" ))
					{
						SFAppData.changeExternalId( "131", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2777" ))
					{
						SFAppData.changeExternalId( "132", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2774" ))
					{
						SFAppData.changeExternalId( "133", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2773" ))
					{
						SFAppData.changeExternalId( "134", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2771" ))
					{
						SFAppData.changeExternalId( "135", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2770" ))
					{
						SFAppData.changeExternalId( "136", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2769" ))
					{
						SFAppData.changeExternalId( "137", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2766" ))
					{
						SFAppData.changeExternalId( "138", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2762" ))
					{
						SFAppData.changeExternalId( "139", data.get( "id" ) );
					} else if (data.get( "primaryId" ).equals( "2760" ))
					{
						SFAppData.changeExternalId( "140", data.get( "id" ) );
					}
				}
				Log.e( "BACKGROUND SYNC", "WILL SYNC ID: " + objectId + " type = " + type );

				if (type.equals( "post" ) || type.equals( "newsletter" ))
				{
					//Log.d("mtag","Hit process object post ");
					this.mHavePosts = true;
					SFPostModel model = SFPostModel.objectFromExternalId( objectId );
					reorderPosts = true;

					SFNewsletterModel asNewsletter = SFNewsletterModel.objectFromExternalId( objectId );
					if (asNewsletter.mId > 0)
					{
						asNewsletter.remove();
						reorderNewsletters = true;
					}

					String image = data.get( "thumbnail" );
					String title = Html.fromHtml( data.get( "title" ) ).toString();
					String content = data.get( "content" );
					int postDate = AiSTr.denullifyInt( data.get( "date" ) );
					String includedIn = data.get( "includedIn" );
					String categories = data.get( "cats" );
					int wpId = AiSTr.denullifyInt( data.get( "primaryId" ) );

					try
					{
						image = URLDecoder.decode( image, "UTF-8" );
						title = URLDecoder.decode( title, "UTF-8" );
						content = URLDecoder.decode( content, "UTF-8" );
						includedIn = URLDecoder.decode( includedIn, "UTF-8" );
						categories = URLDecoder.decode( categories, "UTF-8" );

					} catch (Exception e)
					{
						continue;
					}

					model.mTitle = title;
					model.mContent = content;
					model.mPostDate = postDate;
					model.mExternalId = objectId;
					model.mThumbnailOrigin = image;
					model.mIncludedIn = includedIn;
					model.mCats = categories;
					model.mWpId = wpId;
					if (model.mIncludedIn != null)
					{
						model.saveMembers( true );
					}


					//Log.d( "mTag", "added image hash of " + objectId );
					appData.hashImage( objectId );
					model.saveHashValue();
				} else if (type.equals( "connect" ))
				{
					//this.mMustRefresh = true;
					String email = "";
					String phone = "";
					String facebook = "";
					String twitter = "";
					String content = "";

					try
					{
						email = URLDecoder.decode( data.get( "email" ), "UTF-8" );
						phone = URLDecoder.decode( data.get( "phone" ), "UTF-8" );
						facebook = URLDecoder.decode( data.get( "facebook" ), "UTF-8" );
						twitter = URLDecoder.decode( data.get( "twitter" ), "UTF-8" );
						content = URLDecoder.decode( data.get( "connectcontent" ), "UTF-8" );
					} catch (Exception e)
					{
						e.printStackTrace();

					}

					HashMap<String, String> connectData = new HashMap<String, String>();
					connectData.put( "email", email.trim() );
					connectData.put( "phone", phone.trim() );
					connectData.put( "facebook", facebook.trim() );
					connectData.put( "twitter", twitter.trim() );
					connectData.put( "content", content.trim() );
					connectData.put( "includedin", data.get( "includedIn" ) );
					connectData.put( "externalid", data.get( "id" ) );
					connectData.put( "primaryId", data.get( "primaryId" ) );

					String[] keys = {"email", "phone", "facebook", "twitter", "content"};
					appData.saveMetaData( connectData, keys );
					//hash here because we don't have the sync_id stored
					hashConnect( AiSTr.denullifyInt( data.get( "id" ) ), AiSTr.denullifyInt( data.get( "primaryId" ) ), type );
				} else if (type.equals( "location" ))
				{
					this.mMustRefresh = true;
					String address = "";
					String state = "";
					String city = "";
					String zip = "";

					try
					{
						address = URLDecoder.decode( data.get( "address" ), "UTF-8" );
						state = URLDecoder.decode( data.get( "state" ), "UTF-8" );
						city = URLDecoder.decode( data.get( "city" ), "UTF-8" );
						zip = URLDecoder.decode( data.get( "zip" ), "UTF-8" );
					} catch (Exception e)
					{
						e.printStackTrace();
					}

					HashMap<String, String> locationData = new HashMap<String, String>();
					locationData.put( "address", address.trim() );
					locationData.put( "state", state.trim() );
					locationData.put( "city", city.trim() );
					locationData.put( "zip", zip.trim() );
					locationData.put( "includedin", data.get( "includedIn" ) );
					locationData.put( "externalid", data.get( "id" ) );
					locationData.put( "primaryId", data.get( "primaryId" ) );

					String[] keys = {"address", "city", "state", "zip", "externalid"};
					appData.saveMetaData( locationData, keys );
					//hash here because we don't have the sync_id stored
					hashLocation( AiSTr.denullifyInt( data.get( "id" ) ), AiSTr.denullifyInt( data.get( "includedIn" ) ), type );
				} else if (type.equals( "donate" ) || type.equals( "link" ))
				{
					//Log.d("mtag","Hit process object donate ");
					this.mMustRefresh = true;

					String donateurl = "";

					try
					{
						donateurl = URLDecoder.decode( data.get( "donateurl" ), "UTF-8" );
					} catch (Exception e)
					{

					}

					HashMap<String, String> urlData = new HashMap<String, String>();
					urlData.put( "donateurl", donateurl.trim() );
					urlData.put( "externalid", data.get( "id" ) );
					urlData.put( "primaryId", data.get( "primaryId" ) );

					//String[] keys = {"donateurl","externalid"};
					appData.saveLinkData( urlData );

					//hash here because we don't have the sync_id stored
					hashLink( AiSTr.denullifyInt( data.get( "id" ) ), AiSTr.denullifyInt( data.get( "primaryId" ) ), type );
				} else if (type.equals( "theme" ))
				{
					this.mMustRefresh = true;

					String theme = "";
					String navcolor = "";

					try
					{
						theme = URLDecoder.decode( data.get( "theme" ), "UTF-8" );
						navcolor = URLDecoder.decode( data.get( "navcolor" ), "UTF-8" );
					} catch (Exception e)
					{
					}

					HashMap<String, String> themeData = new HashMap<String, String>();
					themeData.put( "theme", theme.trim() );
					themeData.put( "navcolor", navcolor.trim() );

					appData.saveThemeFromHashMap( themeData );
					addHash( 0, type );
				} else if (type.equals( "playlist" ))
				{
					//Log.d("mtag","Hit process object playlist ");
					this.mHaveSermons = true;
					SFSermonSeriesModel model = SFSermonSeriesModel.objectFromExternalId( objectId );
					boolean replaceImage = true;
					reorderPlaylists = true;

					String image = data.get( "image" );
					String preacher = data.get( "primaryId" );
					String series = data.get( "secondaryId" );
					String includedIn = data.get( "includedIn" );

					try
					{
						image = URLDecoder.decode( image, "UTF-8" );
						preacher = Html.fromHtml( URLDecoder.decode( preacher, "UTF-8" ) ).toString();
						series = Html.fromHtml( URLDecoder.decode( series, "UTF-8" ) ).toString();
					} catch (Exception e)
					{

					}

					model.mTitle = series;
					model.mPreacher = preacher;
					model.mExternalId = objectId;
					model.mThumbnailOrigin = image;
					model.mSermonsIncludedIn = includedIn;

					model.saveMembers( true );
					appData.hashImage( objectId );
					model.saveHashValue();

					//Log.d( "mTag", "added image hash of " + objectId );

				} else if (type.equals( "stream" ))
				{
					//this.mMustRefresh = true;
					String streamId = "";
					String source = "";
					String content = "";
					String url = "";

					try
					{
						streamId = URLDecoder.decode( data.get( "streamid" ), "UTF-8" );
						source = URLDecoder.decode( data.get( "source" ), "UTF-8" );
						content = URLDecoder.decode( data.get( "content" ), "UTF-8" );
						url = URLDecoder.decode( data.get( "url" ), "UTF-8" );

					} catch (Exception e)
					{
						e.printStackTrace();
					}

					HashMap<String, String> streamData = new HashMap<String, String>();
					streamData.put( "streamid", streamId.trim() );
					streamData.put( "url", url.trim() );
					streamData.put( "content", content.trim() );
					streamData.put( "source", source.trim() );
					streamData.put( "includedin", data.get( "includedIn" ) );
					streamData.put( "externalid", data.get( "id" ) );
					streamData.put( "memberid", data.get( "memberId" ) );

					String[] keys = {"streamid", "url", "content", "source", "memberid", "externalid"};
					appData.saveMetaData( streamData, keys );
					//hash here because we don't have the sync_id stored
					hashStream( AiSTr.denullifyInt( data.get( "id" ) ), AiSTr.denullifyInt( data.get( "includedIn" ) ), type );
				}
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}

		// process sermons separately because they won't be useful until all the playlists are in
		for (int i = 0; i < objects.size(); i++)
		{
			HashMap<String, String> data = objects.get( i );

			String type = data.get( "type" );
			int objectId = Integer.valueOf( data.get( "id" ) );

			if ( type.equals( "sermon" ) )
			{
				Log.e( "BACKGROUND SYNC", "WILL SYNC SERMON ID: " + objectId );

				int success = SFSermonModel.saveSermonFromSyncObject( data );
				if( success == 1 )
				{
					this.mHaveSermons = true;
					reorderSermons = true;
				}

				//addHash( objectId, type );
			} // if( type.equals( "sermon" ) )
		} // for( int i = 0; i < objects.size(); i++ )



	}

	/**
	 * finish sync and reorder sections that need it
	 */
	public void finishSync()
	{
		if (reorderNewsletters)
		{
			SFNewsletterModel.setOrder();
		}

		if (reorderPlaylists || reorderSermons)
		{
			SFSermonModel.setOrder();
		}

		if (reorderPosts)
		{
			SFPostModel.setOrder();
		}

		//this.closeDialog( true );
		stopRefreshAnimation();
		if(mCaller.isDoingFullSync)
		{
			SFSharedUIMethods.dismissSyncingOverlay();
		}

		if (sectionsChanged || themeChanged)
		{
			SFSharedUIMethods.restartApp();
		}

		mCaller.isSyncing = false;
		mCaller.isDoingFullSync = false;
	}


	/**
	 * check if the screen needs to be refreshed
	 */
	public void checkForRefresh()
	{
		//Log.d("mTag", "hit checkForRefresh reprder newsletters = " + reorderNewsletters + ": reorder playlists = " + reorderPlaylists + ": reorder sermons = " + reorderSermons + ": reorder posts = " + reorderPosts);
		if( reorderNewsletters )
		{
			SFNewsletterModel.setOrder();
			this.mMustRefresh = true;
		}

		if( reorderPlaylists || reorderSermons )
		{
			SFSermonModel.setOrder();
			this.mMustRefresh = true;
		}

		if( reorderPosts )
		{
			SFPostModel.setOrder();
			this.mMustRefresh = true;
		}

		this.closeDialog( true );
	}

	/**
	 * parses Json objects and returns them in a HashMap
	 * @param object
	 * @param input
	 * @return
	 * @throws JSONException
     */
	public static HashMap<String,String> parseJson( JSONObject object, HashMap<String,String> input ) throws JSONException
	{
		Iterator<String> keys = object.keys();
		while( keys.hasNext() )
		{
			String key = keys.next();
			String stringValue = null;
			try
			{
				JSONObject value = object.getJSONObject( key );
				SFBackgroundSync.parseJson( value, input );
			}
			catch( Exception e )
			{
				stringValue = object.getString( key );
			}

			if( stringValue != null )
			{
				input.put( key, stringValue );
			}
		}


		return input;
	}

	/**
	 *
	 * @param object
	 * @return
	 * @throws JSONException
	 */
	public static ArrayList<HashMap<String,String>> parseJsonArray( JSONArray object) throws JSONException
	{
		ArrayList<HashMap<String,String>> returnValue = new ArrayList<HashMap<String, String>>();
		for( int i = 0; i < object.length(); i ++ )
		{
			HashMap<String,String> hashMap = new HashMap<String, String>(  );
			hashMap = parseJson((JSONObject) object.get( i ),hashMap );
			returnValue.add( hashMap );
			//Log.d("mTag","hashmap = " + hashMap);
		}


		/*Iterator<HashMap<String,String>> keys = object.get(  );
		while( keys.hasNext() )
		{
			String key = keys.next();
			String stringValue = null;
			try
			{
				JSONObject value = object.getJSONObject( key );
				SFBackgroundSync.parseJsonArray( value, input );
			}
			catch( Exception e )
			{
				stringValue = object.getString( key );
			}

			if( stringValue != null )
			{
				input.put( key, stringValue );
			}
		}*/


		return returnValue;
	}

	/**
	 * Download task to run in the background
	 */
	private class DownloadTask extends AsyncTask<URL, Integer, Long>
	{
		public SFApplication mCaller;
		public String mResult;
		public String mNextAction;
		public ArrayList<HashMap<String,String>> mSyncData;
		public DownloadTask mSelfCaller;
		public int objectCount;
		public boolean mHadError = false;
		public boolean mFullSync;

		/**
		 * Constructor
		 * @param caller
		 * @param isFullSync
		 */
		public DownloadTask( SFApplication caller, boolean isFullSync )
		{
			this.mCaller = caller;
			this.mResult = "";
			this.mNextAction = "";
			this.objectCount = 0;
			this.mSyncData = new ArrayList<HashMap<String,String>>();
			this.mFullSync = isFullSync;
		}

		/**
		 *
		 * @param urls
		 * @return
		 */
		@Override
		protected Long doInBackground( URL... urls )
		{
			try
			{
				BufferedReader in = new BufferedReader( new InputStreamReader( urls[0].openStream() ) );
				String line;

				while( ( line = in.readLine() ) != null )
				{
					if( line.equalsIgnoreCase( "bye" ) )
					{
						Log.e( "SOCKET ERROR", "Socket said goodbye" );
					}
					//get lines
					this.mResult += line;
				}
				//Log.d( "mTag","results = " + mResult );
				in.close();
			}
			catch( Exception e )
			{
				this.mHadError = true;
				String message = SFApplication.getInstance().getString( R.string.sync_error ) + e.getLocalizedMessage();
				SFApplication.makeToast( message );
			}

			int count = urls.length;
			long totalSize = 0;
			for( int i = 0; i < count; i++ )
			{
				//totalSize += Downloader.downloadFile( urls[i] );
				publishProgress( (int) ((i / (float) count) * 100) );
				// Escape early if cancel() is called
				if( isCancelled() ) { break; }
			}
			return totalSize;
		}

		@Override
		protected void onProgressUpdate( Integer... progress )
		{
			//setProgressPercent(progress[0]);
		}

		/**
		 *
		 * @param result
		 */
		// run the next piece of the chain
		@Override
		protected void onPostExecute( Long result )
		{
			final DownloadTask callingTask = this;

			// onPostExecute runs on the main thread, which will pause the UI if we allow it to do so
			Thread t = new Thread(
					new Runnable()
					{
						public void run()
						{
							callingTask.runNextAction();
						}
					}

			);
			t.start();
		}

		/**
		 * a list of actions to run
		 */
		public void runNextAction()
		{
			if( this.mHadError )
			{
				SFBackgroundSync sync = new SFBackgroundSync( mCaller );
				sync.mFullSync = this.mFullSync;
				sync.mHadError = true;
				sync.closeDialog( true );
			}
			else if( this.mNextAction.equals( "addObjects" ) )
			{
				this.addObjects();
			}
			else if( this.mNextAction.equals( "addInboxItems" ))
			{
				this.addInboxItems();
			}
			else if( this.mNextAction.equals( "addBibles" ))
			{
				this.addBibles();
			}
			else if( this.mNextAction.equals( "addCalendars" ))
			{
				this.addCalendars();
			}
		}

		/**
		 * data of objects to sync
		 * @param objectIds
		 */
		public void getObjects( ArrayList<Integer> objectIds )
		{

			JSONArray jsonArray = new JSONArray( objectIds );

			//testing
			//jsonArray.put("sections");
			//Log.d("mTag","Hit getObjects sections changed");

			if( themeChanged )
			{
				jsonArray.put("theme");
			}
			if( sectionsChanged )
			{
				jsonArray.put("sections");
				//Log.d("mTag","Hit getObjects sections changed");
			}

			try
			{
				String uaid = SFConstants.UAID_KEY;
				//String demoEmail = this.mCaller.getPreference( "demoemail" );
				String address = SFApplication.getInstance().getResources().getString( R.string.appserve_url ) + "?action=getObjectGroup&i=" + uaid + "&oid=" + jsonArray;
				//if( SFConstants.IS_DEMO )
				//{
				//	uaid = this.mCaller.getPreference( "sitekey" );
				//	address = SFConstants.DEMO_APPSERVE_URL + "?action=getObject&i=" + uaid + "&oid=" + objectIds[i];
				//}
				mNextAction = "addObjects";
				URL url = new URL( address );
				//Log.d("mTag","URL = " + address);
				this.execute( url );
				//Log.d( "mTag", "end of getObjects with id = " + jsonArray.get( 0 ));
			}
			catch( Exception e )
			{
				//String message = SFMainActivity.instance.getString( R.string.sync_error ) + e.getLocalizedMessage();
				//SFMainActivity.makeToast( message );
			}
		}

		/**
		 * process the list of objects
		 */
		private void addObjects()
		{
			if( this.mResult.length() > 0 )
			{
				try
				{
					//Log.d("mtag","DATA_STREAM DUMP = " +mResult);
					JSONArray jsonArray = new JSONArray( this.mResult );

					//testing
				/*int count = jsonArray.length();
				Log.d("mTag","jsonArray count = " + count);
				for( int i = 0; i < count; i++)
				{
					Log.d("mTag", "jsonArray at " + i + ": " + jsonArray.get( i ).toString());
				}*/

					ArrayList<HashMap<String, String>> objectHashmaps = new ArrayList<HashMap<String, String>>();
					JSONObject jsonObject = new JSONObject();
					for (int i = 0; i < jsonArray.length(); i++)
					{
						HashMap<String, String> hashMapObject = new HashMap<String, String>();
						hashMapObject = parseJson( (JSONObject) jsonArray.get( i ), hashMapObject );
						objectHashmaps.add( parseJson( (JSONObject) jsonArray.get( i ), hashMapObject ) );
					}

					//testing
				/*count = objectHashmaps.size();
				Log.d( "mTag", "objectHashmaps count = " + count );
				for( int i = 0; i < count; i++)
				{
					Log.d("mTag", "objectHashmaps at " + i + ": " + objectHashmaps.get( i ).toString());
				}*/

					processObjects( objectHashmaps );
					//checkForRefresh();
				} catch (JSONException e)
				{
					e.printStackTrace();
				}
			}
			DownloadTask inboxTask = new DownloadTask( mCaller, true );
			inboxTask.getInbox();
		}

		/**
		 * get inbox items data to sync
		 */
		public void getInbox()
		{
			if(mInboxChanged)
			{
				try
				{
					String uaid = SFConstants.UAID_KEY;
					//String demoEmail = this.mCaller.getPreference( "demoemail" );
					String address = SFApplication.getInstance().getResources().getString( R.string.appserve_url ) + "?action=getObjectGroup&i=" + uaid + "&oid=[\"inbox\"]";
					//if( SFConstants.IS_DEMO )
					//{
					//	uaid = this.mCaller.getPreference( "sitekey" );
					//	address = SFConstants.DEMO_APPSERVE_URL + "?action=getObject&i=" + uaid + "&oid=" + objectIds[i];
					//}
					mNextAction = "addInboxItems";

					//Log.d( "mTag", "INBOX: " + address );

					URL url = new URL( address );
					//Log.d("mTag","URL = " + address);
					this.execute( url );
					//Log.d( "mTag", "end of getObjects with id = " + jsonArray.get( 0 ));
				} catch (Exception e)
				{
					//String message = SFMainActivity.instance.getString( R.string.sync_error ) + e.getLocalizedMessage();
					//SFMainActivity.makeToast( message );
				}
			}
			else
			{
				getBible();
			}
		}

		/**
		 * process list of inbox items
		 */
		private void addInboxItems()
		{
			try
			{
				JSONArray jsonArray = new JSONArray( this.mResult );

				ArrayList<HashMap<String,String>> objectHashmaps = new ArrayList<HashMap<String, String>>(  );
				for( int i = 0; i < jsonArray.length(); i++ )
				{
					HashMap<String,String> hashMapObject = new HashMap<String, String>(  );
					hashMapObject = parseJson( (JSONObject) jsonArray.get( i ),hashMapObject );
					objectHashmaps.add( parseJson( (JSONObject) jsonArray.get( i ), hashMapObject ) );
				}

				HashMap<String,String>  input = objectHashmaps.get( 0 );
				jsonArray = new JSONArray( input.get( "response" ) );
				objectHashmaps = new ArrayList<HashMap<String, String>>(  );
				for( int i = 0; i < jsonArray.length(); i++ )
				{
					HashMap<String,String> hashMapObject = new HashMap<String, String>(  );
					hashMapObject = parseJson( (JSONObject) jsonArray.get( i ),hashMapObject );
					objectHashmaps.add( parseJson( (JSONObject) jsonArray.get( i ), hashMapObject ) );
				}

				SFAppData appData = new SFAppData();
				appData.processInbox( objectHashmaps );

			} catch (JSONException e)
			{
				e.printStackTrace();
			}
			//if handling push end and go to inbox
			if(SFApplication.getInstance().mHavePush)
			{
				SFApplication.getInstance().mHavePush = false;
				SFApplication.getInstance().currentSectionPosition = -1;
				SFSharedUIMethods.swapToViewWithTag( "inbox", -1 );
			}

			else
			{
				DownloadTask bibleTask = new DownloadTask( mCaller, true );
				bibleTask.getBible();
			}
		}

		/**
		 * get data of Bible to sync
		 */
		public void getBible()
		{
			if(mBibleChanged)
			{
				try
				{
					JSONArray jsonArray = new JSONArray( bibleIds );

					String uaid = SFConstants.UAID_KEY;
					//String demoEmail = this.mCaller.getPreference( "demoemail" );
					String address = SFApplication.getInstance().getResources().getString( R.string.appserve_url ) + "?action=getObjectGroup&i=" + uaid + "&oid=" + jsonArray;
					//if( SFConstants.IS_DEMO )
					//{
					//	uaid = this.mCaller.getPreference( "sitekey" );
					//	address = SFConstants.DEMO_APPSERVE_URL + "?action=getObject&i=" + uaid + "&oid=" + objectIds[i];
					//}
					mNextAction = "addBibles";

					//Log.d( "mTag", "BIBLE: " + address );

					URL url = new URL( address );
					//Log.d("mTag","URL = " + address);
					this.execute( url );
					//Log.d( "mTag", "end of getObjects with id = " + jsonArray.get( 0 ));
				} catch (Exception e)
				{
					//String message = SFMainActivity.instance.getString( R.string.sync_error ) + e.getLocalizedMessage();
					//SFMainActivity.makeToast( message );
				}
			}
			else
			{
				getCalendars();
			}
		}

		/**
		 * process the Bible items to sync
		 */
		private void addBibles()
		{
			SFAppData appData = new SFAppData();
			//Log.d( "mtag","HIT add bibles" );
			try
			{
				JSONArray jsonArray = new JSONArray( this.mResult );


				//Log.d( "mtag","bible jsonArray start = " + jsonArray.toString() );
				if( jsonArray.length() > 0 )
				{
					JSONObject bibleData = jsonArray.getJSONObject( 0 );
					JSONArray versionsRaw = (JSONArray) bibleData.get( "version" );
					Object primaryIdObject = bibleData.get("primaryId");
					Integer primaryId = AiSTr.denullifyInt( (String) primaryIdObject );
					Object idObject = bibleData.get("id");
					Integer id = (Integer) idObject;
					//Log.d( "mTag","primaryId = " + primaryId );
					String[] versions = new String[versionsRaw.length()];
					String[] onDeviceBibleVersions = appData.getBibleVersionsOnDevice();
					for (int i = 0; i < versionsRaw.length(); i++)
					{
						boolean haveVersion = false;
						versions[i] =(String) versionsRaw.get( i );
						for( int j = 0; j < onDeviceBibleVersions.length; j++ )
						{
							if(versions[i].equals( onDeviceBibleVersions[j] ))
							{
								haveVersion = true;
								break;
							}
						}
						if(!haveVersion)
						{
							//add bible
							JSONObject versionData= (JSONObject) bibleData.get( versions[i] );
							appData.addBibleVersion( versionData, versions[i] );
							//Log.d( "mtag","versionData = " +versionData.toString() );
						}
					}
					for(int i = 0; i < onDeviceBibleVersions.length; i++ )
					{
						boolean removeVersion = true;

						for( int j = 0; j < versions.length; j++ )
						{
							if( onDeviceBibleVersions[i].equals( versions[j] ))
							{
								removeVersion = false;
								break;
							}
						}
						if(removeVersion)
						{
							//remove
							appData.removeBibleVersion( onDeviceBibleVersions[i] );
							//Log.d("mtag","remove " + onDeviceBibleVersions[i]);
						}
					}
					appData.holyHash(primaryId,id);
				}

			} catch (JSONException e)
			{
				e.printStackTrace();
			}
			getCalendars();
		}

		/**
		 * get data for the calendars to sync
		 */
		public void getCalendars()
		{
			if(mCalendarChanged)
			{
				try
				{
					JSONArray jsonArray = new JSONArray( mCalendarIds );

					String uaid = SFConstants.UAID_KEY;
					//String demoEmail = this.mCaller.getPreference( "demoemail" );
					String address = SFApplication.getInstance().getResources().getString( R.string.appserve_url ) + "?action=getObjectGroup&i=" + uaid + "&oid=" + jsonArray;
					//if( SFConstants.IS_DEMO )
					//{
					//	uaid = this.mCaller.getPreference( "sitekey" );
					//	address = SFConstants.DEMO_APPSERVE_URL + "?action=getObject&i=" + uaid + "&oid=" + objectIds[i];
					//}
					mNextAction = "addCalendars";

					//Log.d( "mTag", "BIBLE: " + address );

					URL url = new URL( address );
					//Log.d("mTag","URL = " + address);
					this.execute( url );
					//Log.d( "mTag", "end of getObjects with id = " + jsonArray.get( 0 ));
				} catch (Exception e)
				{
					//String message = SFMainActivity.instance.getString( R.string.sync_error ) + e.getLocalizedMessage();
					//SFMainActivity.makeToast( message );
				}
			}
			else
			{
				finishSync();
			}
		}

		/**
		 * process the calendar items to sync
		 */
		private void addCalendars()
		{
			SFAppData appData = new SFAppData();
			//Log.d( "mtag","HIT add bibles" );
			try
			{
				JSONArray jsonArray = new JSONArray( this.mResult );


				//Log.d( "mtag","Calendar jsonArray start = " + jsonArray.toString() );
				for( int i = 0; i < jsonArray.length(); i++ )
				{
					JSONObject calendarData = jsonArray.getJSONObject( i );

					String parsedContent = (String) calendarData.get( "parsedcontent" );
					String content =(String) calendarData.get( "content" );
					//Log.d( "mtag","content = " + content );
					//JSONObject parsedContent = new JSONObject( parsedContentRaw );
					//String secondaryId = (String) calendarData.get( "secondaryId" );
					int id = (int) calendarData.get( "id" );
					String includedIn = (String) calendarData.get( "includedIn" );
					//String primaryId = (String) calendarData.get( "primaryId" );
					String calendarId = (String) calendarData.get( "calendarid" );
					//int active = (int) calendarData.get( "active" );
					//String type = (String) calendarData.get( "type" );

					//String descriptionRaw = (String) parsedContent.get( "description" );
					//String titleRaw = (String) parsedContent.get( "title" );

					//if( parsedContent.has( "ordereditems" ))
					//{
						//JSONObject orderedItems = (JSONObject) parsedContent.get( "ordereditems" );
						//String orderedItemsString = parsedContent.get( "ordereditems" ).toString();
						appData.addCalendar( calendarId, includedIn, parsedContent, content);
					//// TODO: 11/29/16 hash from local
						appData.hashCalendar( id, includedIn );
					//}
				}
			} catch (JSONException e)
			{
				e.printStackTrace();
			}
			finishSync();
		}

	} // private class DownloadTask extends AsyncTask<URL, Integer, Long>

}