/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library 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 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.resource.adapter.google.gdata;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.teiid.logging.LogManager;
import org.teiid.resource.adapter.google.auth.AuthHeaderFactory;
import org.teiid.translator.google.api.SpreadsheetOperationException;
import org.teiid.translator.google.api.UpdateSet;
import org.teiid.translator.google.api.result.UpdateResult;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.BaseFeed;
import com.google.gdata.data.IEntry;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.util.ServiceException;

/**
 * Spreadsheet browser implemented by gdata-java-client: http://code.google.com/p/gdata-java-client/
 * 
 * This browser authenticates using Client Login. 
 * 
 * @author fnguyen
 *
 */
public class GDataClientLoginAPI {
	private static final int RETRY_DELAY = 3000;
	private SpreadsheetService service;
	private FeedURLFactory factory;
	private AuthHeaderFactory headerFactory = null;

	public void setHeaderFactory(AuthHeaderFactory headerFactory) {
		this.headerFactory = headerFactory;
		service.setHeader("Authorization" , headerFactory.getAuthHeader()); //$NON-NLS-1$
	}

	public GDataClientLoginAPI() {
		service = new SpreadsheetService("GdataSpreadsheetBrowser"); //$NON-NLS-1$
		this.factory = FeedURLFactory.getDefault();
	}

	public SpreadsheetEntry getSpreadsheetEntry(String sheetName, boolean key) {
	    if (key) {
	        try {
                return getSpreadsheetEntry(new URL(factory.getSpreadsheetsFeedUrl(), "full/"+sheetName), SpreadsheetEntry.class);
            } catch (MalformedURLException e) {
                throw new SpreadsheetOperationException(e);
            }
	    }
	    SpreadsheetQuery squery = new SpreadsheetQuery(factory.getSpreadsheetsFeedUrl());
		squery.setTitleExact(true);
		squery.setTitleQuery(sheetName);
		SpreadsheetFeed feed = (SpreadsheetFeed) getSpreadsheetFeedQuery(squery, SpreadsheetFeed.class);
		List<SpreadsheetEntry> entry = feed.getEntries();
		if (entry.size() == 0)
			throw new SpreadsheetOperationException("Couldn't find spreadsheet:" + sheetName);
		
		if (entry.size() > 1) {
		    throw new SpreadsheetOperationException("Multiple worksheets with the given title:" + sheetName + ".  Consider using a sheet key instead.");
		}

		return entry.get(0);
	}
	

	private BaseFeed<?, ?> getSpreadsheetFeedQuery(SpreadsheetQuery squery, Class<? extends BaseFeed<?, ?>> feedClass) {
		try { 
			return service.getFeed(squery, feedClass);
		} catch (Exception ex) {
			try {
				Thread.sleep(RETRY_DELAY);
			} catch (InterruptedException e) {
			}
			// Try to relogin
			reauthenticate();
			try {
				return service.getFeed(squery, SpreadsheetFeed.class);
			} catch (Exception ex2) {
				throw new SpreadsheetOperationException("Error getting spreadsheet feed. Possibly bad authentication or connection problems. " + ex2);
			}
		}
	}
	
    private <E extends IEntry> E getSpreadsheetEntry(URL entryUrl, Class<E> entryClass) {
        try { 
            return service.getEntry(entryUrl, entryClass);
        } catch (Exception ex) {
            try {
                Thread.sleep(RETRY_DELAY);
            } catch (InterruptedException e) {
            }
            // Try to relogin
            reauthenticate();
            try {
                return service.getEntry(entryUrl, entryClass);
            } catch (Exception ex2) {
                throw new SpreadsheetOperationException("Error getting spreadsheet feed. Possibly bad authentication or connection problems. " + ex2);
            }
        }
    }

	private void reauthenticate() {
		headerFactory.login();
		service.setHeader("Authorization", headerFactory.getAuthHeader()); //$NON-NLS-1$
	}
/**
 * Updates spreadsheet using the listfeed. 
 * 
 * @param spreadsheetKey  key that identifies spreadsheet
 * @param worksheetID  id that identifies worksheet
 * @param criteria  update criteria
 * @param updateSet  fields that should be updated
 * @return number of updated rows
 */
	public UpdateResult listFeedUpdate(String spreadsheetKey, String worksheetID, String criteria, List<UpdateSet> updateSet) {
		SpreadsheetQuery query = null;
		try {
			query = new SpreadsheetQuery(factory.getListFeedUrl(spreadsheetKey, worksheetID, "private", "full")); //$NON-NLS-1$ //$NON-NLS-2$
			if (criteria != null) {
			    query.setStringCustomParameter("sq", criteria); //$NON-NLS-1$
			}

		} catch (MalformedURLException e) {
			throw new SpreadsheetOperationException("Error getting spreadsheet URL: " + e);
		}
		ListFeed listfeed = (ListFeed) getSpreadsheetFeedQuery(query, ListFeed.class);
		int counter=0; 
		for (ListEntry row : listfeed.getEntries()) {
			for (UpdateSet set : updateSet) {
				row.getCustomElements().setValueLocal(set.getColumnID(), set.getValue());
			}
			try {
				row.update();
			} catch (IOException e) {
				LogManager.logWarning(this.getClass().getName(), e,"Error occured when updating spreadsheet row");
				continue;
			} catch (ServiceException e2) {
				LogManager.logWarning(this.getClass().getName(), e2,"Error occured when updating spreadsheet row");
				continue;
			}
			counter++;
		}
		return new UpdateResult(listfeed.getEntries().size(), counter);
	}
	/**
	 * Deletes spreadsheet rows using the listfeed. 
	 * 
	 * @param spreadsheetKey  key that identifies spreadsheet
	 * @param worksheetID  id that identifies worksheet
	 * @param criteria  delete criteria
	 * @return number of deleted rows
	 */
	public UpdateResult listFeedDelete(String spreadsheetKey, String worksheetID, String criteria) {
		SpreadsheetQuery query = null;
		try {
			query = new SpreadsheetQuery(factory.getListFeedUrl(spreadsheetKey, worksheetID, "private", "full")); //$NON-NLS-1$ //$NON-NLS-2$
			if (criteria != null) {
			    query.setStringCustomParameter("sq", criteria); //$NON-NLS-1$
			}
		} catch (MalformedURLException e) {
			throw new SpreadsheetOperationException("Error getting spreadsheet URL: " + e);
		}
		ListFeed listfeed = (ListFeed) getSpreadsheetFeedQuery(query, ListFeed.class);
        int counter=0;
		for(int i=listfeed.getEntries().size()-1;i>-1;i--) {
			ListEntry row=listfeed.getEntries().get(i);
			try {
				row.delete();
			} catch (IOException e) {
				LogManager.logWarning(this.getClass().getName(), e,"Error occured when deleting spreadsheet row");
				continue;
			} catch (ServiceException e2) {
				LogManager.logWarning(this.getClass().getName(), e2,"Error occured when deleting spreadsheet row");
				continue;
			}
			counter++;
		}
		return new UpdateResult(listfeed.getEntries().size(), counter);
	}
/**
 * Insert row into spreadsheet using listfeed
 * @param spreadsheetKey  key that identifies spreadsheet
 * @param worksheetID  key that identifies worksheet
 * @param pair name - value pair that should be inserted into spreadsheet
 * @return 1 if the row is successfully inserted
 */
	public UpdateResult listFeedInsert(String spreadsheetKey, String worksheetID, Map<String, String> pair) {
		SpreadsheetQuery query = null;
		try {
			query = new SpreadsheetQuery(factory.getListFeedUrl(spreadsheetKey, worksheetID, "private", "full")); //$NON-NLS-1$ //$NON-NLS-2$

		} catch (MalformedURLException e) {
			throw new SpreadsheetOperationException("Error getting spreadsheet URL: " + e);
		}

		ListEntry row = new ListEntry();
		for (Entry<String, String> entry : pair.entrySet()) {
			row.getCustomElements().setValueLocal(entry.getKey(), entry.getValue());
		}

		try {
			service.insert(query.getFeedUrl(), row);
		} catch (Exception e) {
			throw new SpreadsheetOperationException("Error inserting spreadsheet row: " + e);
		}

		return new UpdateResult(1, 1);
	}

}
