/*******************************************************************************
 * This file is part of BOINC.
 * http://boinc.berkeley.edu
 * Copyright (C) 2016 University of California
 * 
 * BOINC is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 * 
 * BOINC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package edu.berkeley.boinc.attach;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import edu.berkeley.boinc.R;
import edu.berkeley.boinc.client.IMonitor;
import edu.berkeley.boinc.client.Monitor;
import edu.berkeley.boinc.rpc.ProjectInfo;
import edu.berkeley.boinc.utils.Logging;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;

public class SelectionListActivity extends FragmentActivity{

	private ListView lv;
	ArrayList<ProjectListEntry> entries = new ArrayList<ProjectListEntry>();
	ArrayList<ProjectInfo> selected = new ArrayList<ProjectInfo>();
	
	// services
	private IMonitor monitor = null;
	private boolean mIsBound = false;
	private ProjectAttachService attachService = null;
	private boolean asIsBound = false;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
         
        if(Logging.DEBUG) Log.d(Logging.TAG, "AttachProjectListActivity onCreate"); 
        
        doBindService();
		
		// setup layout
        setContentView(R.layout.attach_project_list_layout);  
		lv = (ListView) findViewById(R.id.listview);
    }
    
	@Override
	protected void onDestroy() {
    	if(Logging.VERBOSE) Log.v(Logging.TAG, "AttachProjectListActivity onDestroy");
    	doUnbindService();
	    super.onDestroy();
	}
	
	// check whether user has checked at least a single project
	// shows toast otherwise
	private Boolean checkProjectChecked() {
		for(ProjectListEntry tmp: entries) {
			if(tmp.checked) return true;
		}
    	Toast toast = Toast.makeText(getApplicationContext(), R.string.attachproject_list_header, Toast.LENGTH_SHORT);
    	toast.show();
    	if(Logging.DEBUG) Log.d(Logging.TAG, "AttachProjectListActivity no project selected, stop!"); 
		return false;
	}
	
	// check whether device is online before starting connection attempt
	// as needed for AttachProjectLoginActivity (retrieval of ProjectConfig)
	// note: available internet does not guarantee connection to project server
	// is possible!
	private Boolean checkDeviceOnline() {
	    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
	    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
	    Boolean online = activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting();
	    if(!online) {
	    	Toast toast = Toast.makeText(getApplicationContext(), R.string.attachproject_list_no_internet, Toast.LENGTH_SHORT);
	    	toast.show();
	    	if(Logging.DEBUG) Log.d(Logging.TAG, "AttachProjectListActivity not online, stop!"); 
	    }
	    return online;
	}
	
	// triggered by continue button
	public void continueClicked(View v) {
		if(!checkProjectChecked()) return;
		if(!checkDeviceOnline()) return;
		
		String selectedProjectsDebug = "";
		// get selected projects
		selected.clear();
		for(ProjectListEntry tmp: entries) {
			if(tmp.checked) {
				selected.add(tmp.info);
				selectedProjectsDebug += tmp.info.name + ",";
			}
		}
		if(Logging.DEBUG) Log.d(Logging.TAG, "SelectionListActivity: selected projects: " + selectedProjectsDebug);
		
		attachService.setSelectedProjects(selected); // returns immediately
		
		// start credential input activity
		startActivity(new Intent(this, CredentialInputActivity.class));
	}
	
	private ServiceConnection mMonitorConnection = new ServiceConnection() {
	    public void onServiceConnected(ComponentName className, IBinder service) {
	        // This is called when the connection with the service has been established, getService returns 
	    	// the Monitor object that is needed to call functions.
	        monitor = IMonitor.Stub.asInterface(service);
		    mIsBound = true;
		    
			UpdateProjectListAsyncTask task = new UpdateProjectListAsyncTask();
			task.execute();
	    }

	    public void onServiceDisconnected(ComponentName className) {
	    	// This should not happen
	        monitor = null;
		    mIsBound = false;
	    }
	};
	
	private ServiceConnection mASConnection = new ServiceConnection() {
	    public void onServiceConnected(ComponentName className, IBinder service) {
	        // This is called when the connection with the service has been established, getService returns 
	    	// the Monitor object that is needed to call functions.
	        attachService = ((ProjectAttachService.LocalBinder)service).getService();
		    asIsBound = true;
	    }

	    public void onServiceDisconnected(ComponentName className) {
	    	// This should not happen
	    	attachService = null;
	    	asIsBound = false;
	    }
	};
	
	private void doBindService() {
		// start service to allow setForeground later on...
		startService(new Intent(this, Monitor.class));
	    // Establish a connection with the service, onServiceConnected gets called when
		bindService(new Intent(this, Monitor.class), mMonitorConnection, Service.BIND_AUTO_CREATE);
		// bind to attach service
		bindService(new Intent(this, ProjectAttachService.class), mASConnection, Service.BIND_AUTO_CREATE);
	}

	private void doUnbindService() {
	    if (mIsBound) {
	        // Detach existing connection.
	        unbindService(mMonitorConnection);
	        mIsBound = false;
	    }
	    if (asIsBound) {
	        // Detach existing connection.
	        unbindService(mASConnection);
	        asIsBound = false;
	    }
	}
	
    private class UpdateProjectListAsyncTask extends AsyncTask<Void, Void, ArrayList<ProjectInfo>> {
        @Override
        protected ArrayList<ProjectInfo> doInBackground(Void... arg0) {
            ArrayList<ProjectInfo> data = null;
            boolean retry = true;
            // Try to get the project list for as long as the AsyncTask has not been canceled
            while (retry) {
                try {
                    data = (ArrayList<ProjectInfo>)monitor.getAttachableProjects();
                } catch (RemoteException e) {
                    if (Log.isLoggable(Logging.TAG, Log.WARN)) Log.w(Logging.TAG, e);
                }
                if (super.isCancelled()) return data; // Does not matter if data == null or not
                if (data == null) {
                    if (Logging.WARNING) Log.w(Logging.TAG, "UpdateProjectListAsyncTask: failed to retrieve data, retry....");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        if (Log.isLoggable(Logging.TAG, Log.DEBUG)) Log.d(Logging.TAG, e.getLocalizedMessage(), e);
                    }
                } else retry = false;
            }
            if (Logging.DEBUG) Log.d(Logging.TAG, "monitor.getAttachableProjects returned with " + data.size() + " elements");
            // Clear current ProjectListEntries since we successfully have got new ProjectInfos
            SelectionListActivity.this.entries.clear();
            // Transform ProjectInfos into ProjectListEntries
            for (int i = data.size() - 1; i >= 0; i--) {
                if (super.isCancelled()) return data;
                SelectionListActivity.this.entries.add(new ProjectListEntry(data.get(i)));
            }
            // Set preferred Collator for ProjectListEntries before sorting
            if (SelectionListActivity.ProjectListEntry.collator == null) {
                SelectionListActivity.ProjectListEntry.collator = SelectionListActivity.ProjectListEntry.getCollator();
            }
            // Sort ProjectListEntries off the UI thread
            // Unfortunately, there is no way to stop this sort operation if this AsyncTask gets canceled
            Collections.sort(SelectionListActivity.this.entries);
            // Dispose Collator instance after sorting
            SelectionListActivity.ProjectListEntry.collator = null;
            return data;
        }

        protected final void onPostExecute(final ArrayList<ProjectInfo> result) {
            if (result == null) return;

            SelectionListActivity.this.entries.add(new ProjectListEntry()); // add account manager option to bottom of list
            SelectionListAdapter listAdapter = new SelectionListAdapter(SelectionListActivity.this, R.id.listview,entries);
            lv.setAdapter(listAdapter);
        }
    }
	
    static final class ProjectListEntry implements Comparable<SelectionListActivity.ProjectListEntry> {
		public ProjectInfo info;
		public boolean checked;
		public boolean am; //indicates that element is account manager entry

        /**
         * The {@link Collator} used when comparing {@code ProjectListEntry}s.
         * This member is usually only set when performing comparison operations
         * in bulk. Otherwise, it should be set to {@code null} to avoid having
         * a {@link Collator} instance lingering around when not attaching
         * projects (most of the time). Furthermore, when comparing
         * {@code ProjectListEntry}s in bulk the {@link Collator} instance
         * stored by this member does not need to get reallocated and setup on
         * every comparison.
         *
         * @see SelectionListActivity.ProjectListEntry#getCollator()
         * @see Collator
         */
        static Collator collator;

		public ProjectListEntry(ProjectInfo info) {
			this.info = info;
			this.checked = false;
		}
		
		/**
		 * Creates Account manager list object
		 */
		public ProjectListEntry() {
			this.am = true;
		}

        /**
         * Compares this {@code ProjectListEntry} instance to {@code p} based
         * on {@link ProjectInfo#name}. The comparison is <i>case-insensitive</i>.
         *
         * @param p the {@code ProjectListEntry} to compare to
         * @return {@code 0} if both {@link ProjectInfo#name}s are equal,<br>
         * {@code -1} if {@code this} {@link ProjectInfo#name} comes before
         * {@code p}'s {@link ProjectInfo#name} in the current locale's
         * {@link Collator#getInstance() collation},<br>else {@code 1}
         * @see SelectionListActivity.ProjectListEntry#info
         * @see ProjectInfo#name
         * @see Comparable#compareTo(T)
         */
        public final int compareTo(final SelectionListActivity.ProjectListEntry p) {
            return (SelectionListActivity.ProjectListEntry.collator == null ?
                SelectionListActivity.ProjectListEntry.getCollator() :
                SelectionListActivity.ProjectListEntry.collator).compare(this.info.name, p.info.name);
        }

        /**
         * Gets the preferred locale specific {@link Collator} for comparing {@code ProjectListEntry}s.
         *
         * @return the preferred {@link Collator}
         */
        static final Collator getCollator() {
            final Collator collator;
            (collator = Collator.getInstance()).setStrength(Collator.SECONDARY);
            collator.setDecomposition(Collator.NO_DECOMPOSITION);
            return collator;
        }
	}
}
