/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.tudarmstadt.informatik.hostage.sync.nfc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
import android.nfc.NfcEvent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import de.tudarmstadt.informatik.hostage.R;
import de.tudarmstadt.informatik.hostage.logging.NetworkRecord;
import de.tudarmstadt.informatik.hostage.logging.SyncInfoRecord;
import de.tudarmstadt.informatik.hostage.persistence.HostageDBOpenHelper;
import de.tudarmstadt.informatik.hostage.sync.tracing.TracingSyncService;

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class NFCSync extends Activity implements CreateNdefMessageCallback, OnNdefPushCompleteCallback {
	NfcAdapter mNfcAdapter;
	TextView mInfoText;
	private static final int MESSAGE_SENT = 0x1;
	private static final int SYNC_SUCCESSFUL = 0x2;

	private final Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case MESSAGE_SENT:
				mInfoText.setText("Synchronization data sent, wait for data.");
				Log.i("NFC", "Message sent!");
				break;
			case SYNC_SUCCESSFUL:
				mInfoText.setText("Synchronization successfull!");
				Log.i("NFC", "Message sent!");
				break;
			}
		}
	};
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_nfc);

		mInfoText = (TextView) findViewById(R.id.nfc_text_view);
		// Check for available NFC Adapter
		mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
		if (mNfcAdapter == null) {
			mInfoText.setText("NFC is not available on this device.");
		} else if(!mNfcAdapter.isEnabled()){
			mInfoText.setText("Enable Android Beam before synchronizing.");
		} else {
			mInfoText.setText("Hold phones together to synchronize.");
			// Register callback to set NDEF message
			mNfcAdapter.setNdefPushMessageCallback(this, this);
			// Register callback to listen for message-sent success
			mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
		}
	}

	/**
	 * Implementation for the CreateNdefMessageCallback interface
	 */
	@Override
	public NdefMessage createNdefMessage(NfcEvent event) {
		// Get Networkdata
		HostageDBOpenHelper dbh = new HostageDBOpenHelper(this);
		ArrayList<NetworkRecord> localNetworkInformation = dbh.getNetworkInformation();
		HashMap<String, Long> devices_local = dbh.getSyncDevices();
		ArrayList<SyncInfoRecord> syncInfo = dbh.getSyncInfo();
		
		Log.i("NFC", "Creating Message");
		NdefMessage msg = null;
		try {
			NdefRecord netData = NdefRecord.createMime("application/de.tudarmstadt.informatik.hostage", serialize(localNetworkInformation));
			NdefRecord deviceData = NdefRecord.createMime("application/de.tudarmstadt.informatik.hostage", serialize(devices_local));
			NdefRecord syncData = NdefRecord.createMime("application/de.tudarmstadt.informatik.hostage", serialize(syncInfo));
			msg = new NdefMessage(netData, deviceData, syncData);
					
		} catch (IOException e) {
			e.printStackTrace();
		}
		return msg;
	}

	/**
	 * Implementation for the OnNdefPushCompleteCallback interface
	 */
	@Override
	public void onNdefPushComplete(NfcEvent arg0) {
		// A handler is needed to send messages to the activity when this
		// callback occurs, because it happens from a binder thread
		mHandler.obtainMessage(MESSAGE_SENT).sendToTarget();
	}

	@Override
	public void onNewIntent(Intent intent) {
		// onResume gets called after this to handle the intent
		setIntent(intent);
	}

	// HELPER

	@Override
	public void onResume() {
		super.onResume();
		// Check to see that the Activity started due to an Android Beam
		if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
			processIntent(getIntent());
		}
	}

	/**
	 * Parses the NDEF Message from the intent and prints to the TextView
	 */
	void processIntent(Intent intent) {
		Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
		// only one message sent during the beam
		NdefMessage msg = (NdefMessage) rawMsgs[0];
		// record 0 contains the MIME type, record 1 is the AAR, if present
		Object netData;
		Object deviceData;
		Object syncData;
		Log.i("NFC", "Getting Message!");
		try {
			HostageDBOpenHelper dbh = new HostageDBOpenHelper(this);
			netData = deserialize(msg.getRecords()[0].getPayload());
			deviceData = deserialize(msg.getRecords()[1].getPayload());
			syncData = deserialize(msg.getRecords()[2].getPayload());
			ArrayList<NetworkRecord> remoteNetworkInformation = (ArrayList<NetworkRecord>) netData;
			HashMap<String, Long> devices_remote = (HashMap<String, Long>) deviceData;
			HashMap<String, Long> devices_local = dbh.getSyncDevices();
			ArrayList<SyncInfoRecord> syncInfo = (ArrayList<SyncInfoRecord>) syncData;
			
			long tracing_timestamp = 0;
			if(devices_local.containsKey(TracingSyncService.REMOTE_DEVICE))
				tracing_timestamp = devices_local.get(TracingSyncService.REMOTE_DEVICE);				
				
			for(Iterator<String> i = devices_remote.keySet().iterator(); i.hasNext(); ){
				String key = i.next();
				if((devices_local.containsKey(key) && devices_local.get(key) >= devices_remote.get(key)) 
				    || (tracing_timestamp > devices_remote.get(key))){
					i.remove();
				}
			}

			
			for ( Iterator<SyncInfoRecord> i = syncInfo.iterator(); i.hasNext(); ){
				SyncInfoRecord info = i.next();
				if(devices_remote.containsKey(info.getDeviceID())){
					i.remove();
				}				    
			}	
			
			dbh.updateSyncDevices(devices_remote);
			dbh.updateSyncInfo(syncInfo);
			dbh.updateNetworkInformation(remoteNetworkInformation);
			mHandler.obtainMessage(SYNC_SUCCESSFUL).sendToTarget();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
		ByteArrayInputStream in = new ByteArrayInputStream(data);
		ObjectInputStream is = new ObjectInputStream(in);
		return is.readObject();
	}

	private static byte[] serialize(Object obj) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ObjectOutputStream os = new ObjectOutputStream(out);
		os.writeObject(obj);
		return out.toByteArray();
	}

}
