package IEEERobotics.IOIOAI.VT;

import ioio.lib.api.IOIO;
import ioio.lib.api.IOIOFactory;
import ioio.lib.api.exception.ConnectionLostException;
import ioio.lib.api.exception.IncompatibilityException;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import com.viewpagerindicator.TitlePageIndicator;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.TableRow.LayoutParams;

public class IOIORoboticsActivity extends Activity {
	// Static IOIO instance so other activities can access it
	public static IOIO IOIOInstance_;
	public static Context context_;
	
	public static List<String> subTitles_ = new LinkedList<String>();
	public static List<View> subViews_ = new LinkedList<View>();
	
	// Connection and activity related variables
	private static Boolean _connected = false;
	private static Handler _handler = new Handler();
	private Boolean _connectionReset = true;
	private Thread _connectionMonitor;
	
	private TextView _statusText;
	private ProgressBar _statusProgressBar;
	private LinearLayout _logLayout;
	
	private ViewPagerAdapter _pageAdapter;
	private ViewPager _pager;
	private TitlePageIndicator _pageIndicator;
	
	// Debugging stuff
	private Boolean _debugMode;
	private DebugCode _debuggingCode;
	
	// AI stuff
	private AICode _AICode;
	
	// Motor Control class
	private MotorControl _motorControl;
	
	// Item ID for custom menu items
	private static int menuToggleDebugMonitoring = Menu.FIRST;
	private static int menuOpenSettings = Menu.FIRST+1;
	private static int menuReset = Menu.FIRST+2;
	private static int menuClearLog = Menu.FIRST+3;
	
	private static int _connectionCheckInterval = 500;	// in ms
	
//	/** Called when the menu button is first pressed (creates the blank menu) */
//	@Override
//	public boolean onCreateOptionsMenu(Menu menu) {
//		MenuInflater inflater = getMenuInflater();
//		// Inflate a blank menu as we will populate it ourselves
//	    inflater.inflate(R.menu.mainmenu, menu);
//	    return true;
//	}
	
	/** Called when the menu button is pressed, populates the menu with options */
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		// Clear existing menus and create a new one based on current state
		menu.clear();
		
		if (_debugMode)
			menu.add(0, menuToggleDebugMonitoring, 0, "Turn On Robotics AI");
		else
			menu.add(0, menuToggleDebugMonitoring, 0, "Turn On Debug Mode");
		
		if (_connected)
			menu.add(0, menuReset, 0, "Reset IOIO Board");
		else
			menu.add(0, menuReset, 0, "Remove Debug Tabs");
		
		menu.add(0, menuClearLog, 0, "Clear Logs");
		menu.add(0, menuOpenSettings, 0, "Settings");
		
		return super.onPrepareOptionsMenu(menu);
	}
	
	/** Handles what to do when a menu item is selected */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
//		// Disable certain menu options if IOIO board is not connected
//		if (item.getItemId() == menuReset && 
//				!_connected) {
//			Toast.makeText(this, "IOIO board not connected", Toast.LENGTH_SHORT).show();
//			return true;
//		};
		
		// Otherwise handle menu selections
		if (item.getItemId() == menuToggleDebugMonitoring) {
			if (_debugMode) {
				_debugMode = false;
				logMessage("Operating mode changed to AI");
				if (_connected)
					hardReset();
			} else {
				_debugMode = true;
				logMessage("Operating mode changed to Debug");
				if (_connected)
					hardReset();
			}
			return true;
		} else if (item.getItemId() == menuOpenSettings) {
			startActivity(new Intent(this, GlobalPreferenceActivity.class));
			return true;
		} else if (item.getItemId() == menuReset) {
			if (_connected)
				hardReset();
			else {
				if (_debuggingCode != null) {
					removeDebugView();
				}
			}
			return true;
		} else if (item.getItemId() == menuClearLog) {
			_logLayout.removeAllViews();
			return true;
		} else {
			return super.onOptionsItemSelected(item);
		}
	}
	
	/** Called when the application is started */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // Grab the local context so other classes can use it to create views
        context_ = this.getApplicationContext();
        
        // Get local pointers to views
        _pageAdapter = new ViewPagerAdapter(this);
        _pager = (ViewPager)findViewById(R.id.viewpager);
        _pageIndicator = (TitlePageIndicator)findViewById(R.id.indicator);
        _statusText = (TextView)findViewById(R.id.statusText);
        _statusProgressBar = (ProgressBar)findViewById(R.id.progressBar);
        
        _pager.setAdapter(_pageAdapter);
        _pageIndicator.setViewPager(_pager);
        
        createLogView();
        
        // Get default/saved debug mode
        if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_StartInDebug", true))
        	_debugMode = true;
        else
        	_debugMode = false;
        
        // Print out current debug status
        if (_debugMode)
        	logMessage("Debug mode is currently on");
        else
        	logMessage("Debug mode is currently off");
        
        // Start connection monitoring thread for the IOIO board
        connectToIOIOBoard();        
    }
    
    /** Called when the application is stopped */
	@Override
	public void onDestroy() {
		super.onDestroy();
		connectionReset();
		if (_debuggingCode != null) {
			removeDebugView();
		}
		_connectionMonitor.interrupt();
		IOIOInstance_.disconnect();
		_connected = false;
	}

	/** Starts the thread that monitors the connection to the IOIO board */
    private void connectToIOIOBoard() {
    	// Create a new thread to monitor the connection status to the IOIO board
    	_connectionMonitor = new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						// Check if a new instance of IOIO needs to be created
						// Reset is only triggered when a disconnect happens
						if (_connectionReset) {
							updateStatus("Waiting to connect to the IOIO board...");
							toggleProgressOn();
							IOIOInstance_ = IOIOFactory.create();
							_connectionReset = false;
						}
						// Check if thread should keep running
						if (Thread.currentThread().isInterrupted()) {
							break;
						}
						// Attempt to connect to the IOIO board
						// If the board was disconnected, ConnectionLostException is thrown while waiting
						IOIOInstance_.waitForConnect();
						// Do stuff when the first connection to the board is made
						if (!_connected) {
							updateStatus("Connected to the IOIO board");
							Thread.sleep(200);
							connectionSuccess();
						}
						_connected = true;
						toggleProgressOff();
						Thread.sleep(_connectionCheckInterval);
					} catch (ConnectionLostException e) {
						// Reset the connection so reconnection attempts can be made
						_connected = false;
						_connectionReset = true;
						connectionReset();
						updateStatus("Connection to the IOIO board has be lost");
					} catch (IncompatibilityException e) {
						// Throw error and quit
						_connected = false;
						_connectionReset = true;
						connectionReset();
						updateStatus("Connected IOIO board is incompatible");
					} catch (InterruptedException e) {
						break;
					}
				}
			}
		});
    	_connectionMonitor.start();
    }
    
    /** Called when the connection to the IOIO board succeeds */
    private void connectionSuccess() {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				// If debug mode is enabled, run the debug code to create graphs
				if (_debugMode) {
		    		startDebugMode();
		    	} else {
		    		startAIMode();
		    	}
			}
		});
    }
    
    /** Called when the connection to the IOIO board has been reset */
    private void connectionReset() {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				// If the debugger code was running, stop the updating thread
				if (_debuggingCode != null)
					_debuggingCode.stopPollingThread();
				if (_AICode != null)
					_AICode.stopPollingThread();
				if (_motorControl != null) {
		    		_motorControl.disconnect();
		    	}
			}
		});
    }
    
    /** Creates the first layout for the ViewPager */
    private void createLogView() {
    	if (subTitles_.contains("Logger")) {
    		removeView(subViews_.get(subTitles_.indexOf("Logger")));
    		subTitles_.remove("Logger");
    	}
    	
		_logLayout = new LinearLayout(this);
		_logLayout.setOrientation(LinearLayout.VERTICAL);
		_logLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
	
		ScrollView scroller = new ScrollView(this);
		scroller.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
		scroller.addView(_logLayout);
		
		addView("Logger", scroller);
	}

    /** Creates a new page in the ViewPager */
	private void addView(String name, View view) {
		// Add the view and label to the lists
		subTitles_.add(subTitles_.size(), name);
		subViews_.add(subViews_.size(), view);
		
		// Update the screen with the new views
		_pageAdapter.notifyDataSetChanged();
		_pageIndicator.notifyDataSetChanged();
	}

	/** Removes a page in the ViewPager */
	private void removeView(View view) {
		if (subViews_.contains(view)) {
			// Change the main view to the first one (logger)
			_pager.setCurrentItem(0);
			
			// Remove the view and label from the lists
	    	subTitles_.remove(subViews_.indexOf(view));
			subViews_.remove(view);
			
			// Update the screen with the new views
			_pageAdapter.notifyDataSetChanged();
			_pageIndicator.notifyDataSetChanged();
		}
	}

	/** Start point for code to execute in Debug Mode */
    private void startDebugMode() {
    	// Run debug code to create graphs
		logMessage("Executing Debug Mode");
		
		// On first run, create inputs and views
		if (_debuggingCode == null) {
			_debuggingCode = new DebugCode(this);
			addView("Graphs", _debuggingCode.getView());
		}
		if (_motorControl == null) {
			_motorControl = new MotorControl(this);
			addView("Motor", _motorControl.getDebugView());
		}
		
		_debuggingCode.Run();
//		_motorControl.Run();
    }
    
    /** Call to stop the debug code and remove all associated views */
    private void removeDebugView() {
    	removeView(_debuggingCode.getView());
    	removeView(_motorControl.getDebugView());
    	
    	if (_debuggingCode != null) {
//			_debuggingCode.stopPollingThread();
    		_debuggingCode = null;
    	}
    	if (_motorControl != null) {
			_motorControl = null;
		}
    }
    
    /** Start point for code to execut`e in AI Mode */
    private void startAIMode() {
    	// Remove the debug views if they exist
    	if (_debuggingCode != null) {
    		removeDebugView();
    	}
    	
    	logMessage("Executing AI Mode");
    	_AICode = new AICode(this);
    	_AICode.Run();
    }
    
    /**
     * A hard reset is exactly like physically powering off the IOIO 
     * board and powering it back on. As a result, the connection 
     * with the IOIO will drop and the IOIO instance will become 
     * disconnected and unusable. The board will perform a full reboot, 
     * including going through the bootloader sequence, i.e. an attempt 
     * to update the board's firmware will be made.
     */
    private void hardReset() {
		try {
			IOIOInstance_.hardReset();
		} catch (ConnectionLostException e) {
			_connected = false;
			_connectionReset = true;
			connectionReset();
			updateStatus("Hard reset executed on the IOIO board");
		}
    }
    
    /** Sets the status TextView to the provided message */
    private void updateStatus(final String message) {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				logMessage(message);
				_statusText.setText("Status: " + message);
			}
		});
    }
    
    /** Appends the provided message to the beginning of the log TextView */
    public void logMessage(final String message) {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				// Get the current time
				SimpleDateFormat dateFormat = new SimpleDateFormat("[HH:mm:ss]");
				Date now = new Date();
				String currentTime = dateFormat.format(now);
				// Log message with the current time appended to the front
				String msg = currentTime + " " + message;
				TextView tv = new TextView(context_);
				tv.setText(msg);
				_logLayout.addView(tv,0);
			}
		});
    }
    
    /** Returns the current connection state */
    public Boolean getConnected() {
    	return _connected;
    }
    
    /** Toggles the visibility of the status ProgressBar */
    private void toggleProgressOn() {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				_statusProgressBar.setVisibility(View.VISIBLE);
			}
		});
    }
    
    /** Toggles the visibility of the status ProgressBar */
    private void toggleProgressOff() {
    	_handler.post(new Runnable() {
			@Override
			public void run() {
				_statusProgressBar.setVisibility(View.GONE);
			}
		});
    }
}