package com.calsignlabs.apde.vcs;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import androidx.appcompat.app.AlertDialog;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;

import com.calsignlabs.apde.APDE;
import com.calsignlabs.apde.R;
import com.calsignlabs.apde.task.DeleteFileTask;
import com.calsignlabs.apde.task.Task;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;

public class GitRepository {
	public static final String MASTER_BRANCH = "master";
	public static final String REMOTE_NAME = "origin";
	
	private File rootDir;
	private Git git;
	
	public GitRepository(File dir) {
		rootDir = dir;
		
		open();
	}
	
	public boolean exists() {
		return getGitDir().exists();
	}
	
	public void initRepo() {
		if (exists()) {
			return;
		}
		
		try {
			Git.init().setDirectory(getRootDir()).setBare(false).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Adds a rule to the .gitignore file. Creates this file if it does not exist.
	 * 
	 * @param rules
	 */
	public void addIgnoreRules(String[] rules) {
		File gitignoreFile = new File(rootDir, ".gitignore");
		
		FileOutputStream fileOut = null;
		BufferedOutputStream out = null;
		
		try {
			fileOut = new FileOutputStream(gitignoreFile, true);
			out = new BufferedOutputStream(fileOut);
			
			for (String rule : rules) {
				out.write((rule + "\n").getBytes());
			}
			
			out.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null) {
					out.close();
				}
				
				if (fileOut != null) {
					fileOut.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void cloneRepo(String uri, File cloneTo) {
		Git result = null;
		
		try {
			result = Git.cloneRepository().setURI(uri).setDirectory(cloneTo)
					.setBranch(MASTER_BRANCH).setBare(false).setRemote(REMOTE_NAME)
					.setNoCheckout(false).setCloneAllBranches(false).setCloneSubmodules(false).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (result != null) {
				result.close();
			}
		}
	}
	
	public static void cloneRepo(String uri, File cloneTo, String branch) {
		Git result = null;
		
		try {
			result = Git.cloneRepository().setURI(uri).setDirectory(cloneTo)
					.setBranch(branch).setBare(false).setRemote(REMOTE_NAME)
					.setNoCheckout(false).setCloneAllBranches(false).setCloneSubmodules(false).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (result != null) {
				result.close();
			}
		}
	}
	
	public static void cloneRepo(String uri, File cloneTo, String branch, GitUser user) {
		Git result = null;
		
		try {
			result = Git.cloneRepository().setURI(uri).setDirectory(cloneTo)
					.setBranch(branch).setBare(false).setRemote(REMOTE_NAME)
					.setNoCheckout(false).setCloneAllBranches(false).setCloneSubmodules(false)
					.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.getUsername(), user.getPassword())).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (result != null) {
				result.close();
			}
		}
	}
	
	/**
	 * Note: This is only used by the examples repo
	 * 
	 * @return
	 */
	public boolean canPull(String remoteURI, String remoteBranch) {
		//Determine whether or not the remote repository has new commits that need to be pulled
		//This just compares the local and remote HEADs - if they're different, then we can pull
		
		try {
			Collection<Ref> refs = Git.lsRemoteRepository().setHeads(true).setTags(true).setRemote(remoteURI).call();
			
			ObjectId remoteHead = null;
			
			for (Ref ref : refs) {
				if (ref.getName().equals("refs/heads/" + remoteBranch)) {
					remoteHead = ref.getObjectId();
					break;
				}
			}
			
			ObjectId localHead = git.getRepository().resolve("HEAD");
			
			if (remoteHead == null) {
				return false;
			}
			
			if (localHead == null) {
				return true;
			}
			
			return !remoteHead.toString().equals(localHead.toString());
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (MissingObjectException e) {
			e.printStackTrace();
		} catch (IncorrectObjectTypeException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	public void pullRepo() {
		try {
			git.pull().setRemote(REMOTE_NAME).setRemoteBranchName(MASTER_BRANCH).setRebase(false).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		}
	}
	
	public void pullRepo(String uri, String branch) {
		try {
			setRemote(uri);
			git.pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch).setRebase(false).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		}
	}
	
	public void pullRepo(String uri, String branch, GitUser user) {
		try {
			setRemote(uri);
			git.pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch).setRebase(false)
					.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.getUsername(), user.getPassword())).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		}
	}
	
	public void pushRepo(String uri, GitUser user) {
		try {
			setRemote(uri);
			git.push().setRemote(REMOTE_NAME).setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.getUsername(), user.getPassword())).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		}
	}
	
	public void addAndCommit(String message, GitUser user) {
		try {
			git.add().addFilepattern(".").call();
			git.commit().setMessage(message).setAuthor(user.getName(), user.getEmail()).setCommitter(user.getName(), user.getEmail()).setAll(true).call();
		} catch (InvalidRemoteException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Get a list of recent commits
	 * 
	 * @param num the number of commits to get, use -1 to get all commits
	 * @return the list of commits
	 */
	public ArrayList<RevCommit> getRecentCommits(int num) {
		ArrayList<RevCommit> commits = new ArrayList<RevCommit>();
		
		try {
			Iterator<RevCommit> it;
			
			//Pass in -1 to get all commits
			if (num == -1) {
				it = git.log().add(git.getRepository().resolve(Constants.HEAD)).call().iterator();
			} else {
				it = git.log().add(git.getRepository().resolve(Constants.HEAD)).setMaxCount(num).call().iterator();
			}
			
			while (it.hasNext()) {
				commits.add(it.next());
			}
			
			return commits;
		} catch (AmbiguousObjectException e) {
			e.printStackTrace();
		} catch (IncorrectObjectTypeException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			//This is thrown when there are no commits / there is not head
			//Is there a better way to deal with this?
		}
		
		return commits;
	}
	
	public ArrayList<CharSequence> getRecentCommitMessages(int num, int truncateAt) {
		ArrayList<RevCommit> commits = getRecentCommits(num);
		ArrayList<CharSequence> commitMessages = new ArrayList<CharSequence>(commits.size());
		
		for (RevCommit commit : commits) {
			//Ellipsize if necessary
			commitMessages.add(ellipsizeCommitMessage(commit, truncateAt));
		}
		
		return commitMessages;
	}
	
	public ArrayList<CharSequence> getRecentCommitMessages(ArrayList<RevCommit> commits, int truncateAt) {
		ArrayList<CharSequence> commitMessages = new ArrayList<CharSequence>(commits.size());
		
		for (RevCommit commit : commits) {
			//Ellipsize if necessary
			commitMessages.add(ellipsizeCommitMessage(commit, truncateAt));
		}

		return commitMessages;
	}
	
	public static String ellipsizeCommitMessage(RevCommit commit, int truncateAt) {
		String shortMessage = commit.getShortMessage();
		
		return truncateAt > 0 ?
				(shortMessage.length() > truncateAt
						? shortMessage.substring(0, Math.min(truncateAt, shortMessage.length())) + "…"
						: shortMessage)
				: commit.getFullMessage();
	}
	
	public DiffFormatter getDiffFormatter(OutputStream out) {
		DiffFormatter formatter = new DiffFormatter(out);
		formatter.setRepository(git.getRepository());
		formatter.setDiffComparator(RawTextComparator.DEFAULT);
		formatter.setDetectRenames(true);
		
		return formatter;
	}
	
	/**
	 * @param context the application context
	 * @return a list of possible actions for this repository
	 */
    public ArrayList<GitAction> getActions(final APDE context) {
        ArrayList<GitAction> actions = new ArrayList<GitAction>();
		
        if (exists()) {
			actions.add(new GitAction(context, this, R.string.git_pull) {
				@Override
				public void run() {
					final LinearLayout layout = (LinearLayout) inflateLayout(context, R.layout.git_pull);
					
					String origin = getRemote();
					
					if (origin != null) {
						//If the repository already has a remote, then we can fill it in
						((EditText) layout.findViewById(R.id.git_pull_remote)).setText(origin);
					}
					
					GitUser savedUser = new GitUser(context);
					((EditText) layout.findViewById(R.id.git_credentials_username)).setText(savedUser.getUsername());
					
					showLayoutAlert(context.getEditor(), R.string.git_pull, layout, R.string.git_pull_button, new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int button) {
							final String remote = ((EditText) layout.findViewById(R.id.git_pull_remote)).getText().toString();

							final String username = ((EditText) layout.findViewById(R.id.git_credentials_username)).getText().toString();
							final char[] password = ((EditText) layout.findViewById(R.id.git_credentials_password)).getText().toString().toCharArray();
							
							context.getTaskManager().launchTask("gitPullTask", true, context.getEditor(), true, new Task() {
								@Override
								public void run() {
									postStatus(context.getResources().getString(R.string.git_task_pull_begin));
									
									if (username.length() > 0 || password.length > 0) {
										GitUser user = new GitUser(username, password, "", "");
										
										repo.pullRepo(remote, MASTER_BRANCH, user);
										
										user.saveUser(context);
									} else {
										repo.pullRepo(remote, MASTER_BRANCH);
									}
									
									context.getEditor().runOnUiThread(new Runnable() {
										@Override
										public void run() {
											//Update for any changes
											context.getEditor().reloadSketch();
										}
									});
									
									postStatus(context.getResources().getString(R.string.git_task_pull_finish));
								}
								
								@Override
								public CharSequence getTitle() {
									return context.getResources().getString(R.string.git_pull);
								}
							});
						}
					});
				}
			});
			
			actions.add(new GitAction(context, this, R.string.git_push) {
				@Override
				public void run() {
					final LinearLayout layout = (LinearLayout) inflateLayout(context, R.layout.git_push);
					
					String origin = getRemote();
					
					if (origin != null) {
						//If the repository already has a remote, then we can fill it in
						((EditText) layout.findViewById(R.id.git_push_remote)).setText(origin);
					}

					GitUser savedUser = new GitUser(context);
					((EditText) layout.findViewById(R.id.git_credentials_username)).setText(savedUser.getUsername());
					
					showLayoutAlert(context.getEditor(), R.string.git_push, layout, R.string.git_push_button, new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int button) {
							final String remote = ((EditText) layout.findViewById(R.id.git_push_remote)).getText().toString();
							
							final String username = ((EditText) layout.findViewById(R.id.git_credentials_username)).getText().toString();
							final char[] password = ((EditText) layout.findViewById(R.id.git_credentials_password)).getText().toString().toCharArray();
							
							context.getTaskManager().launchTask("gitPushTask", true, context.getEditor(), true, new Task() {
								@Override
								public void run() {
									postStatus(context.getResources().getString(R.string.git_task_push_begin));
									
									GitUser user = new GitUser(username, password, "", "");
									
									repo.pushRepo(remote, user);
									
									user.saveUser(context);
									
									postStatus(context.getResources().getString(R.string.git_task_push_finish));
								}
								
								@Override
								public CharSequence getTitle() {
									return context.getResources().getString(R.string.git_push);
								}
							});
						}
					});
				}
			});
			
			actions.add(new GitAction(context, this, R.string.git_snapshot) {
				@Override
				public void run() {
					final LinearLayout layout = (LinearLayout) inflateLayout(context, R.layout.git_commit);
					
					GitUser savedUser = new GitUser(context);
					((EditText) layout.findViewById(R.id.git_user_info_name)).setText(savedUser.getName());
					((EditText) layout.findViewById(R.id.git_user_info_email)).setText(savedUser.getEmail());
					
					showLayoutAlert(context.getEditor(), R.string.git_snapshot, layout, R.string.git_snapshot_button, new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int button) {
							final String message = ((EditText) layout.findViewById(R.id.git_commit_message)).getText().toString();
							
							final String name = ((EditText) layout.findViewById(R.id.git_user_info_name)).getText().toString();
							final String email = ((EditText) layout.findViewById(R.id.git_user_info_email)).getText().toString();
							
							context.getEditor().runOnUiThread(new Runnable() {
								@Override
								public void run() {
									//Make sure we use the most updated version of the files
									context.getEditor().autoSave();
									
									context.getTaskManager().launchTask("gitAddCommitTask", false, null, true, new Task() {
										@Override
										public void run() {
											postStatus(context.getResources().getString(R.string.git_task_snapshot_begin));
											
											GitUser user = new GitUser("", new char[0], name, email);
											
											repo.addAndCommit(message, user);
											
											user.saveUser(context);
											
											postStatus(context.getResources().getString(R.string.git_task_snapshot_finish));
										}
										
										@Override
										public CharSequence getTitle() {
											return context.getResources().getString(R.string.git_snapshot);
										}
									});
								}
							});
						}
					});
				}
			});
			
			actions.add(new GitAction(context, this, R.string.git_history) {
				@Override
				public void run() {
					Intent gitHistoryIntent = new Intent(context, GitHistoryActivity.class);
					context.getEditor().startActivity(gitHistoryIntent);
				}
			});
			
			actions.add(new GitAction(context, this, R.string.git_delete_repo) {
				@Override
				public void run() {
					AlertDialog.Builder builder = new AlertDialog.Builder(context.getEditor());
					
					builder.setTitle(R.string.git_delete_repo_confirmation_dialog_title);
					builder.setMessage(String.format(Locale.US, context.getResources().getString(R.string.git_delete_repo_confirmation_dialog_message), context.getSketchName()));
					
					builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							launchGitDeleteTask(context, repo);
						}
					});
					
					builder.setNeutralButton(R.string.cancel, new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {}
					});
					
					builder.create().show();
				}
			});
        } else {
			if (context.getSketchLocationType() == APDE.SketchLocation.SKETCHBOOK || context.getSketchLocationType() == APDE.SketchLocation.EXTERNAL) {
				actions.add(new GitAction(context, this, R.string.git_init_repo) {
					@Override
					public void run() {
						context.getTaskManager().launchTask("gitInitTask", false, null, true, new Task() {
							@Override
							public void run() {
								postStatus(context.getResources().getString(R.string.git_task_init_begin));
								
								repo.initRepo();
								
								postStatus(context.getResources().getString(R.string.git_task_init_gitignore_begin));
								
								addIgnoreRules(new String[] {"bin/**"});
								
								postStatus(context.getResources().getString(R.string.git_task_init_finish));
							}
							
							@Override
							public CharSequence getTitle() {
								return context.getResources().getString(R.string.git_init_repo);
							}
							
							@Override
							public void cancel() {
								//Undo progress by deleting the repository
								launchGitDeleteTask(context, repo);
							}
						});
					}
				});
			}
		}
		
		actions.add(new GitAction(context, this, R.string.git_clone_repo) {
			@Override
			public void run() {
				final LinearLayout layout = (LinearLayout) inflateLayout(context, R.layout.git_clone);
				
				GitUser savedUser = new GitUser(context);
				((EditText) layout.findViewById(R.id.git_credentials_username)).setText(savedUser.getUsername());
				
				showLayoutAlert(context.getEditor(), R.string.git_clone_repo, layout, R.string.git_clone_button, new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int button) {
						final String remote = ((EditText) layout.findViewById(R.id.git_clone_remote)).getText().toString();
						final String localName = ((EditText) layout.findViewById(R.id.git_clone_local_name)).getText().toString();
						
						final File destDir = new File(context.getSketchbookFolder(), localName);
						
						final String username = ((EditText) layout.findViewById(R.id.git_credentials_username)).getText().toString();
						final char[] password = ((EditText) layout.findViewById(R.id.git_credentials_password)).getText().toString().toCharArray();
						
						context.getTaskManager().launchTask("gitCloneTask", true, context.getEditor(), true, new Task() {
							@Override
							public void run() {
								postStatus(context.getResources().getString(R.string.git_task_clone_begin));
								
								if (username.length() > 0 || password.length > 0) {
									GitUser user = new GitUser(username, password, "", "");
									
									cloneRepo(remote, destDir, MASTER_BRANCH, user);
									
									user.saveUser(context);
								} else {
									cloneRepo(remote, destDir, MASTER_BRANCH);
								}
								
								postStatus(context.getResources().getString(R.string.git_task_clone_finish));
								
								context.getEditor().runOnUiThread(new Runnable() {
									@Override
									public void run() {
										context.getEditor().forceDrawerReload();
									}
								});
							}
							
							@Override
							public CharSequence getTitle() {
								return context.getResources().getString(R.string.git_clone_repo);
							}
							
							@Override
							public void cancel() {
								//Undo progress by deleting the folder
								context.getTaskManager().launchTask("gitCloneUndoTask", false, null, true,
										new DeleteFileTask(destDir, context.getResources().getString(R.string.git_delete_repo), null,
												context.getResources().getString(R.string.git_task_delete_begin),
												context.getResources().getString(R.string.git_task_delete_finish),
												context.getResources().getString(R.string.git_task_delete_fail)));
							}
						});
					}
				});
			}
		});
		
        return actions;
    }
	
	public void launchGitDeleteTask(APDE context, GitRepository repo) {
		context.getTaskManager().launchTask("gitDeleteTask", false, null, true,
				new DeleteFileTask(repo.getGitDir(), context.getResources().getString(R.string.git_delete_repo), null,
						context.getResources().getString(R.string.git_task_delete_begin),
						context.getResources().getString(R.string.git_task_delete_finish),
						context.getResources().getString(R.string.git_task_delete_fail)));
	}
	
	private View inflateLayout(Context context, int layoutId) {
		return View.inflate(new ContextThemeWrapper(context, R.style.Theme_AppCompat_Dialog), layoutId, null);
	}
	
	private void showLayoutAlert(Activity context, int titleId, View layout, int positiveButtonTitleId, DialogInterface.OnClickListener positiveListener) {
		AlertDialog.Builder builder = new AlertDialog.Builder(context);
		builder.setTitle(titleId);
		
		builder.setView(layout);
		
		builder.setPositiveButton(positiveButtonTitleId, positiveListener);
		builder.setNegativeButton(R.string.cancel, null);
		
		builder.create().show();
	}
	
    /**
     * @return a list of names of possible actions for this repository
     */
    public String[] getActionNames(ArrayList<GitAction> actions) {
        String[] actionNames = new String[actions.size()];
		
        for (int i = 0; i < actions.size(); i ++) {
            actionNames[i] = actions.get(i).getName();
        }
		
        return actionNames;
    }
	
    /**
     * @return a list of names of possible actions for this repository
     */
    public String[] getActionNames(APDE context) {
        ArrayList<GitAction> actions = getActions(context);
        String[] actionNames = new String[actions.size()];
		
        for (int i = 0; i < actions.size(); i ++) {
            actionNames[i] = actions.get(i).getName();
        }
		
        return actionNames;
    }
	
    public abstract class GitAction implements Runnable {
		protected APDE context;
        protected GitRepository repo;
		protected String name;
		
        public GitAction(APDE context, GitRepository repo, String name) {
			this.context = context;
            this.repo = repo;
			this.name = name;
        }
		
		public GitAction(APDE context, GitRepository repo, int nameId) {
			this.context = context;
			this.repo = repo;
			name = context.getResources().getString(nameId);
		}
		
        public String getName() {
			return name;
		}
    }
	
	public void close() {
		git.close();
	}
	
    public void open() {
        try {
            git = new Git(new FileRepository(getGitDir()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	
	public File getRootDir() {
		return rootDir;
	}
	
	public File getGitDir() {
		return new File(rootDir, ".git");
	}
	
	public Git getGit() {
		return git;
	}
	
	public StoredConfig getConfig() {
		return git.getRepository().getConfig();
	}
	
	public void setRemote(String remote) {
		String currentRemote = getRemote();
		
		if (currentRemote != null && currentRemote.equals(remote)) {
			return;
		}
		
		try {
			StoredConfig config = getConfig();
			config.setString("remote", REMOTE_NAME, "url", remote);
			config.save();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public String getRemote() {
		return getConfig().getString("remote", REMOTE_NAME, "url");
	}
}