package de.tudresden.slr.model.mendeley.api.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IDecoratorManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.jbibtex.BibTeXDatabase;
import org.jbibtex.BibTeXEntry;
import org.jbibtex.BibTeXParser;
import org.jbibtex.CharacterFilterReader;
import org.jbibtex.Key;
import org.jbibtex.ParseException;
import org.jbibtex.StringValue;
import org.jbibtex.TokenMgrException;
import org.jbibtex.Value;

import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
import com.google.api.client.auth.oauth2.RefreshTokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.http.BasicAuthentication;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import de.tudresden.slr.model.mendeley.Activator;
import de.tudresden.slr.model.mendeley.api.model.MendeleyAnnotation;
import de.tudresden.slr.model.mendeley.api.model.MendeleyDocument;
import de.tudresden.slr.model.mendeley.api.model.MendeleyFolder;
import de.tudresden.slr.model.mendeley.preferences.PreferenceConstants;
import de.tudresden.slr.model.mendeley.synchronisation.WorkspaceBibTexEntry;
import de.tudresden.slr.model.mendeley.synchronisation.WorkspaceManager;
import de.tudresden.slr.model.mendeley.ui.MendeleyOAuthDialog;

/**
 * The MendeleyClient class implements a client that communicates with the Mendeley API. 
 * 
 * Tokens will be obtained and transmitted to the Preference Store. 
 * There is only a single instance of this class.
 *  
 * @author Johannes Pflugmacher
 * @version 1.0
 *
 */
public class MendeleyClient {

	/**
	 * URL for requesting the Mendeley OAuth Dialog.
	 */
	private static final String oauth_request_url = "https://api.mendeley.com/oauth/authorize";
	
	/**
     * URL for requesting OAuth access tokens.
     */
    private static final String token_request_url = "https://api.mendeley.com/oauth/token";

    /**
     * Client ID of client credential.
     */
    private static final String client_id = "4335";

    /**
     * Client secret of client credential.
     */
    private static final String client_secret = "sSFcbUA38RS9Cpm7";

    /**
     * Redirect URI
     */
    private static final String redirect_uri = "https://localhost";
    
    /*
     * Authorization Code
     */
    private String auth_code = "";
    
    /*
     * Access Token
     */
    private String access_token;
    
    /*
     * Refresh Token
     */
    private String refresh_token;
    
    /*
     * Expiration date of the obtained Access Token 
     */
    private Calendar expires_at;
    
    /*
     * WorkspaceManager to access the Resources of the Projects
     */
    private WorkspaceManager wm = WorkspaceManager.getInstance();
    
    /**
     * Latest Mendeley Folders of User that is currently logged into his Mendeley profile
     */
    private MendeleyFolder[] mendeleyFolders = null;
    
    private IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        
    private static final class InstanceHolder {
      static final MendeleyClient INSTANCE = new MendeleyClient();
      
    }

    // Prevent the generation of this object by other methods
    private MendeleyClient () {}
   
    public static MendeleyClient getInstance () {
      return InstanceHolder.INSTANCE;
    }
    
    public void setAccess_token(String access_token) {
		this.access_token = access_token;
	}
    
    public String getAccess_token() {
		return access_token;
	}
    
    public void setRefresh_token(String refresh_token) {
		this.refresh_token = refresh_token;
	}
    
    public String getRefresh_token() {
		return refresh_token;
	}
    
    public String getAuth_code() {
		return auth_code;
	}
    
    public void setAuth_code(String auth_code) {
		this.auth_code = auth_code;
	}
   

    /**
     * This method is used to creates a URL to the OAuth authorization service
     * @return String This returns the URL needed to call the Mendeley Login Screen
     */
    public final String getAuthURL(){
    	return 	oauth_request_url + "?" +
    			"&client_id=" + client_id +
    			"&redirect_uri=" + redirect_uri +
    			"&response_type=code&scope=all";
    }
    
    /**
     * This methods is used to obtain all documents via the GET https://api.mendeley.com/documents endpoint.
     * 
     * @return This returns a BibTeX String of all documents from the /documents endpoint
     */
    public String getAllDocumentsBibTex() {
    	refreshTokenIfNecessary();
    	
    	/**
    	 *  The limit parameter of obtainable documents is restricted to a maximum of 500. 
    	 *  The view parameter determines the richness of fields that will be given back.
    	 */
    	String resource_url = "https://api.mendeley.com/documents?&view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn;
		try {
			resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
			resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
	        resource_cxn.setRequestMethod("GET");
	        resource_cxn.setRequestProperty("Accept","application/x-bibtex");
	        
	        InputStream resource = resource_cxn.getInputStream();
	    	
	        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
	        String line = null;
	        String bibtex_str = "";
	        while ((line = r.readLine()) != null) {
	        	bibtex_str = bibtex_str + line;
	        }
	        return bibtex_str;
		} catch (IOException e) {
			e.printStackTrace();
			return "";
		}
		
    }
    
    /**
     * This methods is used to obtain all documents via the GET https://api.mendeley.com/documents endpoint.
     * 
     * @return This returns a JSON String of all documents from the /documents endpoint
     */
    public String getAllDocumentsJSON() throws MalformedURLException, IOException, TokenMgrException, ParseException{
    	refreshTokenIfNecessary();
    	
    	String resource_url = "https://api.mendeley.com/documents?&view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        
        InputStream resource = resource_cxn.getInputStream();
    	
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        String line = null;
        String json_str = "";
        while ((line = r.readLine()) != null) {
        	json_str = json_str + line;
        }
        
    	return json_str;
    }
    
    /**
     * This methods is used to obtain all Annotations of a Mendeley Document via the GET https://api.mendeley.com/annotations endpoint.
     * 
     * @param document Pass a Mendeley Document to get its annotation
     * @return This method returns the annotations stored in Mendeley from the document you pass to this method
     * @throws MalformedURLException
     * @throws IOException
     */
    public MendeleyAnnotation[] getAnnotations(MendeleyDocument document) throws MalformedURLException, IOException{
    	refreshTokenIfNecessary();
    	
    	String result = "";
    	// Use parameter "document_id" to get annoations from a specific document
    	String resource_url = "https://api.mendeley.com/annotations?document_id=" + document.getId();
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        
        InputStream resource = resource_cxn.getInputStream();
        
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        String line = null;
        while ((line = r.readLine()) != null) {
        	result = result + line;
        }
        
        Gson gson = new Gson();
        MendeleyAnnotation[] annotations = gson.fromJson(result, MendeleyAnnotation[].class);
        
        return annotations;
    }
    
    /**
     * This methods is used to obtain all BibTeXEntries of a Mendeley Document via the GET https://api.mendeley.com/documents/{id} endpoint.
     * 
     * @param id Document ID tha is stored in Mendeley Folder.
     * @return This Method returns a BibTeXDatabase of the given document id
     * @throws MalformedURLException
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public BibTeXDatabase getDocumentBibTex(String id) throws MalformedURLException, IOException, TokenMgrException, ParseException{
    	refreshTokenIfNecessary();
    	
    	String resource_url = "https://api.mendeley.com/documents/" + id + "?view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        resource_cxn.setRequestProperty("Accept","application/x-bibtex");
        
        InputStream resource = resource_cxn.getInputStream();
    	
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        BibTeXDatabase db = new BibTeXDatabase();
    	try(Reader reader = r){
    		CharacterFilterReader filterReader = new CharacterFilterReader(reader);
    		BibTeXParser parser = new BibTeXParser();
    		db = parser.parse(filterReader);
    		
    		/*
    		 * Mendeley API returns a parsing error concerning the 'title' field
    		 * 	- 	The additional characters '{' at the beginning of a Title and '}' at the end of a title
    		 * 		must be removed
    		 */
    		for(BibTeXEntry entry: db.getEntries().values()){
    			String fixedTitleStr = getFixedString(entry.getField(new Key("title")).toUserString());
    			StringValue fixedTitleValue = new StringValue(fixedTitleStr, StringValue.Style.BRACED);
    			entry.removeField(new Key("title"));
    			entry.addField(new Key("title"), fixedTitleValue);
    		}
    	}catch (TokenMgrException | IOException |ParseException e) {
			e.printStackTrace();
		}
 
    	return db;
    }
    
    /**
     * This methods is used to obtain all Folder from a Mendeley profile via the GET https://api.mendeley.com/folders endpoint.
     * The response model of this endpoint only includes name, creation date and id of a folder.
     * 
     * @return This Method returns a JSON String of all Mendeley Folders
     * @throws MalformedURLException
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public String getAllFolders() throws MalformedURLException, IOException, TokenMgrException, ParseException{
    	refreshTokenIfNecessary();
    	
    	String resource_url = "https://api.mendeley.com/folders";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        
        InputStream resource = resource_cxn.getInputStream();
    	
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        String line = null;
        String json_str = "";
        while ((line = r.readLine()) != null) {
        	json_str = json_str + line;
        }
        
    	return json_str;
    }
    
    /**
     * This methods is used to update the Mendeley Folder that are stored in this client class 
     * by obtaining all Folders of a Mendeley profile including the respective Mendeley Documents.
     * The updated Mendeley Folders are a requirement for starting the Mendeley Sync Wizard
     * 
     * @param monitor 
     * @return Returns the IStatus of the Job that is used to inform the User about the current state
     * @throws MalformedURLException
     * @throws TokenMgrException
     * @throws IOException
     * @throws ParseException
     */
    public IStatus updateMendeleyFolders(IProgressMonitor monitor) throws MalformedURLException, TokenMgrException, IOException, ParseException{
    	refreshTokenIfNecessary();
    	
    	// Get all Folder IDs and Folder Names of your Mendeley profile
        String folders_str = this.getAllFolders();
        Gson gson = new Gson();
        
        // Parse JSON String of Folders into MendeleyFolder objects
        MendeleyFolder[] m_folders = gson.fromJson(folders_str, MendeleyFolder[].class);
        
        // Number of tasks equals number of Folder that are present in your Mendeley profile
        SubMonitor subMonitor = SubMonitor.convert(monitor, m_folders.length);
		
        for(int i = 0; i < m_folders.length;i++){
			subMonitor.setTaskName("Working on Folder " + String.valueOf(i+1) + " of " + String.valueOf(m_folders.length) +  ": " + m_folders[i].getName() );
			
			String folder_content_str;
			try {
				// get all documents of a specific folder
				folder_content_str = this.getDocumentsByFolderJSON(m_folders[i].getId());
				
				// get all BibTeXEntries of a specific folder
				BibTeXDatabase bibdb = this.getDocumentsByFolderBibTex(m_folders[i]);
				m_folders[i].setBibtexDatabase(bibdb);
				
				MendeleyDocument[] folder_content = gson.fromJson(folder_content_str, MendeleyDocument[].class);
				m_folders[i].setDocuments(folder_content);
				m_folders[i].setType("Folder");
				
				// get the notes of all Documents to find the 'classes' field that is missing in the Mendeley BibTeX response model
				for(MendeleyDocument md : m_folders[i].getDocuments()){
					subMonitor.subTask("Download Document \"" + md.getTitle() + "\"");
					String notes = md.getNotes();
					MendeleyAnnotation[] annotations = getAnnotations(md);
					for(MendeleyAnnotation annotation: annotations){
						if(annotation.getType().equals("note")){
							notes = annotation.getText();
						}
					}
					// add notes to the 'notes' variable
					md.setNotes(notes);
				}
			} catch (TokenMgrException | IOException | ParseException e) {
				e.printStackTrace();
			}
			subMonitor.worked(1);
			if (subMonitor.isCanceled()) return Status.CANCEL_STATUS;
        }
        mendeleyFolders = m_folders;
		return Status.OK_STATUS;
    }
    
    /**
     * This methods is used to obtain a Mendeley Folder from a specific Folder via the GET https://api.mendeley.com/documents endpoint.
     * All Mendeley Documents included in this folder and the respective BibTeXDatabase will be included.
     * 
     * @param folderId The ID of a Folder that is stored in your Mendeley profile
     * @return This method returns a Mendeley Folder containing all respective documents
     * @throws MalformedURLException
     * @throws TokenMgrException
     * @throws IOException
     * @throws ParseException
     */
    public MendeleyFolder getMendeleyFolder(String folderId) throws MalformedURLException, TokenMgrException, IOException, ParseException{
    	refreshTokenIfNecessary();
    	
    	// The 'folder_id' parameter is used to specify the folder you want to retrieve.
    	String resource_url = "https://api.mendeley.com/documents?folder_id=" + folderId + "&view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        InputStream resource = resource_cxn.getInputStream();
        
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        String line = null;
        String json_str = "";
        while ((line = r.readLine()) != null) {
        	json_str = json_str + line;
        }
        
        Gson gson = new Gson();
        MendeleyDocument[] documents = gson.fromJson(json_str, MendeleyDocument[].class);
        
	    MendeleyFolder m_folder = new MendeleyFolder();
	    m_folder.setId(folderId);
	    m_folder.setDocuments(documents);
	    
        try {
			String folder_content_str = this.getDocumentsByFolderJSON(m_folder.getId());
			
			MendeleyDocument[] folder_content = gson.fromJson(folder_content_str, MendeleyDocument[].class);
			m_folder.setDocuments(folder_content);
			m_folder.setType("Folder");
			
			for(MendeleyDocument md : m_folder.getDocuments()){
				String notes = md.getNotes();
				MendeleyAnnotation[] annotations = getAnnotations(md);
				for(MendeleyAnnotation annotation: annotations){
					if(annotation.getType().equals("note")){
						notes = annotation.getText();
					}
				}
				md.setNotes(notes);
			}
			BibTeXDatabase bibdb = this.getDocumentsByFolderBibTex(m_folder);
			m_folder.setBibtexDatabase(bibdb);
			
		} catch (TokenMgrException | IOException | ParseException e) {
			e.printStackTrace();
		}
        return m_folder;
        
    }
 
    /**
     * This methods is used to obtain all Mendeley documents that are stored in a specific Folder
     * via the GET https://api.mendeley.com/documents endpoint.
     * 
     * @param id Pass the ID of a Mendeley Folder to get its documents
     * @return This Method returns a JSON String of all documents stored in a specific Mendeley Folder
     * @throws MalformedURLException
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public String getDocumentsByFolderJSON(String id) throws MalformedURLException, IOException, TokenMgrException, ParseException{
    	refreshTokenIfNecessary();
    	
    	String resource_url = "https://api.mendeley.com/documents?folder_id=" + id + "&view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        InputStream resource = resource_cxn.getInputStream();
        
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
        String line = null;
        String json_str = "";
        while ((line = r.readLine()) != null) {
        	json_str = json_str + line;
        }
        
    	return json_str;
    }
    
    /**
     * This methods is used to obtain a BibTeXDatabase containing all documents of a specific Mendeley Folder
     * via the GET https://api.mendeley.com/documents endpoint.
     * 
     * @param mf You can pass a specific MendeleyFolder object to get its BibTeXDatabase
     * @return This Method returns a BibTeXDatabase 
     * @throws MalformedURLException
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public BibTeXDatabase getDocumentsByFolderBibTex(MendeleyFolder mf) throws MalformedURLException, IOException, TokenMgrException, ParseException{
    	refreshTokenIfNecessary();
    	
    	String resource_url = "https://api.mendeley.com/documents?folder_id=" + mf.getId() + "&view=bib&limit=500";
    	
    	HttpURLConnection resource_cxn = (HttpURLConnection)(new URL(resource_url).openConnection());
        resource_cxn.addRequestProperty("Authorization", "Bearer " + this.access_token);
        resource_cxn.setRequestMethod("GET");
        resource_cxn.setRequestProperty("Accept","application/x-bibtex");
        InputStream resource = resource_cxn.getInputStream();
            	
        BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));

        BibTeXDatabase db = new BibTeXDatabase();
    	try(Reader reader = r){
    		CharacterFilterReader filterReader = new CharacterFilterReader(reader);
    		BibTeXParser parser = new BibTeXParser();
    		db = parser.parse(filterReader);
    		
    		/*
    		 * Mendeley API returns a parsing error concerning the 'title' field
    		 * 	- 	The additional characters '{' at the beginning of a Title and '}' at the end of a title
    		 * 		must be removed
    		 */
    		for(BibTeXEntry entry: db.getEntries().values()){
    			String fixedTitleStr = getFixedString(entry.getField(new Key("title")).toUserString());
    			StringValue fixedTitleValue = new StringValue(fixedTitleStr, StringValue.Style.BRACED);
    			entry.removeField(new Key("title"));
    			entry.addField(new Key("title"), fixedTitleValue);
    			
    			Value value = entry.getField(new Key("title"));
    			if(value != null){
    				MendeleyDocument document = mf.getDocumentByTitle(value.toUserString());
    				if(document != null){
    					// get the Note containing the additional classes field
    					String notes = document.getNotes();
    					if(notes != null){
    						// Specify regex pattern for classes
    						Pattern pattern = Pattern.compile("\\[classes\\](.*?)\\[/classes\\]");
    						Matcher matcher = pattern.matcher(notes);
    						String classes_str = "";
    						
    						// Extract only classes string inside of [classes] tags
    						if(matcher.find()){
    							classes_str = matcher.group(1);
    							StringValue classes = new StringValue(classes_str, StringValue.Style.BRACED);
    							
    							// Add classes to BibTeXDatabase
    							entry.addField(new Key("classes"), classes);
    						}
    					}
    				}
    			}
    		}
    		
    	}catch (TokenMgrException | IOException |ParseException e) {
			e.printStackTrace();
		}
    	return db;
    }
    
    /**
     * This Methods opens the MendeleyOAuthDialog which sisplay the authorization user interface.
     * 
     * @param shell
     * @return This returns if Login was successful
     */
    public boolean displayAuthorizationUserInterface(Shell shell){
    	
    	MendeleyOAuthDialog oauth_D = new MendeleyOAuthDialog(shell);
		oauth_D.create();
		
		if (oauth_D.open() == Window.OK) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * This Method exchanges the authorization code for an access token. 
     * If successful the Tokens and Expiration Date will be stored.
     * 
     * @param code The authorization code from the user interface response has to be passed
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public void requestAccessToken(String code) throws IOException, TokenMgrException, ParseException {
	    try {
	      TokenResponse response =
	          new AuthorizationCodeTokenRequest(new NetHttpTransport(), new JacksonFactory(),
	              new GenericUrl("https://api.mendeley.com/oauth/token"),code)
	              .setRedirectUri("https://localhost")
	              .setGrantType("authorization_code")
	              .setClientAuthentication(
	                  new BasicAuthentication("4335", "sSFcbUA38RS9Cpm7")).execute();
	      
	      this.access_token = response.getAccessToken();
	      this.refresh_token = response.getRefreshToken();
	      this.expires_at = this.generateExpiresAtFromExpiresIn(response.getExpiresInSeconds().intValue());
	      
	      updatePreferenceStore();
	      refreshTokenIfNecessary();
	    } catch (TokenResponseException e) {
	      if (e.getDetails() != null) {
	        System.err.println("Error: " + e.getDetails().getError());
	        if (e.getDetails().getErrorDescription() != null) {
	          System.err.println(e.getDetails().getErrorDescription());
	        }
	        if (e.getDetails().getErrorUri() != null) {
	          System.err.println(e.getDetails().getErrorUri());
	        }
	      } else {
	        System.err.println(e.getMessage());
	      }
	    }
    }
    
    /**
     * This Methods uses the refresh Token to retrieve a renewed access token
     * 
     * @param code Refresh Token
     * @return This returns if the request was successful.
     * @throws IOException
     * @throws TokenMgrException
     * @throws ParseException
     */
    public boolean requestRefreshAccessToken(String code) throws IOException, TokenMgrException, ParseException {
	    try {
	      RefreshTokenRequest request =
	          new RefreshTokenRequest(new NetHttpTransport(), new JacksonFactory(),
	              new GenericUrl("https://api.mendeley.com/oauth/token"),code)
	          	  .setRefreshToken(code)
	          	  .set("redirect_uri", "https://localhost")
	              .setGrantType("refresh_token")
	              .setClientAuthentication(
	                  new BasicAuthentication("4335", "sSFcbUA38RS9Cpm7"));
	      
	      TokenResponse response = request.execute();
	      
	      this.access_token = response.getAccessToken();
	      this.refresh_token = response.getRefreshToken();
	      this.expires_at = this.generateExpiresAtFromExpiresIn(response.getExpiresInSeconds().intValue());
	      
	      updatePreferenceStore();
	      refreshTokenIfNecessary();
	      
	      return true;
	    } catch (TokenResponseException e) {
	      if (e.getDetails() != null) {
	        System.err.println("Error: " + e.getDetails().getError());
	        if (e.getDetails().getErrorDescription() != null) {
	          System.err.println(e.getDetails().getErrorDescription());
	        }
	        if (e.getDetails().getErrorUri() != null) {
	          System.err.println(e.getDetails().getErrorUri());
	        }
	      } else {
	        System.err.println(e.getMessage());
	      }
	      return false;
	    }
    }
    
    /**
     * This method takes the milliseconds left until the access token expires and
     * generates a Calendar 
     * 
     * @param expiresIn Milliseconds until Token expires
     * @return This return a Calender object for the expiration date
     */
    private Calendar generateExpiresAtFromExpiresIn(int expiresIn) {
        final Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, expiresIn);
        return c;
    }
    
    /**
     * This method is called to check if the token is this valid.
     * The authorization interface will be called if the token is missing
     * or expired.
     * 
     * @return This returns if authorization is valid
     */
    public boolean refreshTokenIfNecessary(){
    	
    	// check if date is in already preference store
    	long date = store.getLong(PreferenceConstants.P_EXPIRE_DATE);
    	if(date != 0) {
    		Calendar preference_date = convertCalender(date);
    		if(expires_at == null) {
    			expires_at = preference_date;
    			access_token = store.getString(PreferenceConstants.P_TOKEN);
    			refresh_token = store.getString(PreferenceConstants.P_REFRESH_TOKEN);
    		}
    		else {
    			// check if expiration date from store is from latest token
    			if(preference_date.before(expires_at)) {
        			expires_at = preference_date;
        			access_token = store.getString(PreferenceConstants.P_TOKEN);
        			refresh_token = store.getString(PreferenceConstants.P_REFRESH_TOKEN);
        		}
    		}
    		
    	}
    	
    	// call auth interface when there is still no expiration date
    	if(expires_at == null) {
    		Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
    		return displayAuthorizationUserInterface(shell);
    	}
    	else {
    		final Calendar now = Calendar.getInstance();
        	
        	if(now.before(expires_at)){
        		final Calendar refresh_due = Calendar.getInstance();
        		refresh_due.setTimeInMillis(expires_at.getTimeInMillis());
        		refresh_due.add(Calendar.SECOND, -1200);
        		
        		// Refresh Token will be used 20 min before token expires
        		if(now.after(refresh_due)){
        			try {
    					return this.requestRefreshAccessToken(this.refresh_token);
    				} catch (TokenMgrException | IOException | ParseException e) {
    					e.printStackTrace();
    				}
        		} 		
        	}
        	
        	// call auth interface when token expired
        	if(now.after(expires_at)){
        		Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        		return displayAuthorizationUserInterface(shell);
        	}
        	return true;
    	}
    }
    
    /**
     * This methods is used to update a Mendeley documents via the PATCH https://api.mendeley.com/documents/{id} endpoint. 
     * @param document Pass the document that needs to be updated
     */
    public void updateDocument(MendeleyDocument document ){
    	refreshTokenIfNecessary();
    	
    	HttpRequestFactory requestFactory = new ApacheHttpTransport().createRequestFactory();
    	HttpRequest request;
    	HttpRequest patch_request;
    	
    	Gson gson = new GsonBuilder().create();
    	String json_body = gson.toJson(document);
    	String document_id = document.getId();
    	String resource_url = "https://api.mendeley.com/documents/" + document_id;
    	GenericUrl gen_url = new GenericUrl(resource_url);
    	
		try {
			final HttpContent content = new ByteArrayContent("application/json", json_body.getBytes("UTF8") );
			patch_request = requestFactory.buildPatchRequest(gen_url, content);
			patch_request.getHeaders().setAuthorization("Bearer " + access_token);
			
			patch_request.getHeaders().setContentType("application/vnd.mendeley-document.1+json");
			String rawResponse = patch_request.execute().parseAsString();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    /**
     * This methods is used to delete a Mendeley documents via the DELETE https://api.mendeley.com/documents/{id} endpoint. 
     * @param document Pass the document that needs to be deleted from Mendeley
     */
    public void deleteDocument(MendeleyDocument document ){
    	refreshTokenIfNecessary();
    	
    	HttpRequestFactory requestFactory = new ApacheHttpTransport().createRequestFactory();
    	HttpRequest request;
    	HttpRequest delete_request;
    	
    	Gson gson = new GsonBuilder().create();
    	String json_body = gson.toJson(document);
    	String document_id = document.getId();
    	String resource_url = "https://api.mendeley.com/documents/" + document_id;
    	GenericUrl gen_url = new GenericUrl(resource_url);
    	
		try {
			final HttpContent content = new ByteArrayContent("application/json", json_body.getBytes("UTF8") );
			delete_request = requestFactory.buildDeleteRequest(gen_url);
			delete_request.getHeaders().setAuthorization("Bearer " + access_token);
			
			delete_request.getHeaders().setContentType("application/vnd.mendeley-document.1+json");
			String rawResponse = delete_request.execute().parseAsString();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    /**
     * This methods is used to add a new document to Mendeley via the POST https://api.mendeley.com/documents endpoint.
     * A successful response contains the newly added MendeleyDocument.
     * 
     * @param entry A BibTeXEntry is needed to add MendeleyDocument
     * @return This returns a MendeleyDocument if request was successful
     */
    public MendeleyDocument addDocument(BibTeXEntry entry ){
    	refreshTokenIfNecessary();
    	
    	HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory();
    	HttpRequest request;
    	HttpRequest patch_request;
    	MendeleyDocument document = null;
    	
    	// create a new MendeleyDocument and add the fields from BibTeXEntry that should be added
    	MendeleyDocument document_to_add = new MendeleyDocument(entry);
    	
    	Gson gson = new GsonBuilder().create();
    	String json_body = gson.toJson(document_to_add);
    	
    	String resource_url = "https://api.mendeley.com/documents";
    	GenericUrl gen_url = new GenericUrl(resource_url); 	
    	
		try {
			final HttpContent content = new ByteArrayContent("application/json", json_body.getBytes("UTF8") );
			
			patch_request = requestFactory.buildPostRequest(gen_url, content);
			patch_request.getHeaders().setAuthorization("Bearer " + access_token);
			patch_request.getHeaders().setContentType("application/vnd.mendeley-document.1+json");
			String rawResponse = patch_request.execute().parseAsString();
			document = gson.fromJson(rawResponse, MendeleyDocument.class);
		} catch (IOException e ) {
			e.printStackTrace();
		}

		return document;
    }
    
    /**
     * This methods is adds a document that exists in Mendeley to a specific folder
     * via the POST https://api.mendeley.com/folders/{id}/documents endpoint.
     * @param document This methods needs a dkocument to add
     * @param folder_id This passes the folder where to document is added to
     */
    public void addDocumentToFolder(MendeleyDocument document, String folder_id){
    	refreshTokenIfNecessary();
    	
    	HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory();
    	HttpRequest request;
    	HttpRequest patch_request;
    	
    	Gson gson = new GsonBuilder().create();
    	String json_body = "{\"id\": \""+ document.getId() + "\" }";
    	String resource_url = "https://api.mendeley.com/folders/"+ folder_id + "/documents";
    	GenericUrl gen_url = new GenericUrl(resource_url);
    		
		try {
			final HttpContent content = new ByteArrayContent("application/json", json_body.getBytes("UTF8") );
			patch_request = requestFactory.buildPostRequest(gen_url, content);
			patch_request.getHeaders().setAuthorization("Bearer " + access_token);
			patch_request.getHeaders().setContentType("application/vnd.mendeley-document.1+json");
			String rawResponse = patch_request.execute().parseAsString();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    /**
     * This methods updates the Preference store when a new token is obtained 
     */
    private void updatePreferenceStore() {
    	store.setValue(PreferenceConstants.P_TOKEN, access_token);
		store.setValue(PreferenceConstants.P_REFRESH_TOKEN, refresh_token);
		store.setValue(PreferenceConstants.P_EXPIRE_DATE, expires_at.getTimeInMillis());
		store.setValue(PreferenceConstants.P_MENDELEY, "mendeley_on");
		try {
			if(store instanceof ScopedPreferenceStore) {
				((ScopedPreferenceStore) store).save();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    private Calendar convertCalender(long milliseconds) {
    	Calendar c = Calendar.getInstance();
    	c.setTimeInMillis(milliseconds);
		return c;
    }
    
    /**
     * This methods removes tokens and expiration date from class and preference store
     * in order to logout the user from mendeley
     * 
     */
    public void logout() {
    	access_token = "";
    	refresh_token = "";
    	expires_at = null;
   
    	store.setValue(PreferenceConstants.P_TOKEN, access_token);
		store.setValue(PreferenceConstants.P_REFRESH_TOKEN, refresh_token);
		store.setValue(PreferenceConstants.P_EXPIRE_DATE, 0L);
		store.setValue(PreferenceConstants.P_MENDELEY, "mendeley_off");
		try {
			if(store instanceof ScopedPreferenceStore) {
				((ScopedPreferenceStore) store).save();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// Remove links between Mendeley Folders and Files after logout
		wm = WorkspaceManager.getInstance();
		for(WorkspaceBibTexEntry entry : wm.getWorkspaceEntries()) {
			entry.setMendeleyFolder(null);
		}
		
		// Remove text decorations from Project Navigation after logout
		IDecoratorManager decoratorManager = PlatformUI.getWorkbench().getDecoratorManager();
		decoratorManager.update("de.tudresden.slr.model.mendeley.decorators.MendeleyOverlayDecorator");
    }
    
    
    public MendeleyFolder[] getMendeleyFolders() {
		return mendeleyFolders;
	}
    
    /**
     * Creates a new String without brackets at the start and end of a string.
     * Method is needed because Mendeley returns the 'title' field of its BibTeXEntries with additional brackets
     * 
     * @param string String that needs to be fixed
     * @return This returns string without additional brackets
     */
    private String getFixedString(String string) {
    	if(string.startsWith("{") & string.endsWith("}")) {
    		return string.substring(1, string.length() - 1);
    	}
    	return string;
    }
}