/*  Copyright (C) 2003-2011 JabRef contributors.
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.imports;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.StringTokenizer;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

import net.sf.jabref.BibtexEntry;
import net.sf.jabref.GUIGlobals;
import net.sf.jabref.Globals;
import net.sf.jabref.OutputPrinter;

/**
 * This class fetches up to 200 citations from JStor by a given search query. It
 * communicates with jstor via HTTP and Cookies. The activeFetcher automates the
 * following steps:
 * <ol>
 * <li>Do a basic search on www.jstor.org</li>
 * <li>Save the first 200 hits</li>
 * <li>Download the saved citations as bibtex</li>
 * <li>Parse it with the BibtexParser</li>
 * <li>Import the BibtexEntrys via the ImportInspectionDialog</li>
 * </ol>
 * 
 * @author Juliane Doege, Tobias Langner
 */
public class JSTORFetcher implements EntryFetcher {

    /**
     * cookies can't save more than 200 citations
     */
    protected static int MAX_CITATIONS = 200;

    /**
     * Cookie key for Jstor ticket (authentication)
     */
    protected static final String COOKIE_TICKET = "Jstor_Ticket";

    /**
     * location where the ticket is obtained
     * 
     */
    protected static final String URL_TICKET = "http://www.jstor.org/search";

    /**
     * Cookie key for citations to be fetched
     * 
     */
    protected static final String COOKIE_CITATIONS = "Jstor_citations0";

    /**
     * location where to obtain the citations cookie
     * 
     */
    protected static final String URL_BIBTEX = "http://www.jstor.org/browse/citations.txt?exportFormat=bibtex&exportAction=Display&frame=noframe&dpi=3&config=jstor&viewCitations=1&View=View";

    public String getHelpPage() {
        return "JSTOR.html";
    }

    public URL getIcon() {
        return GUIGlobals.getIconUrl("www");
    }

    public String getKeyName() {
        return "JSTOR";
    }

    public JPanel getOptionsPanel() {
        // No Options panel
        return null;
    }

    public String getTitle() {
        return "JSTOR";
    }
    
    public void stopFetching() {
        // cannot be interrupted
    }

    public boolean processQuery(String query, ImportInspector dialog, OutputPrinter status) {

        try {
            // First open a ticket with JStor
            String ticket = openTicket();

            // Then execute the query
            String citations = getCitations(ticket, query);

            // Last retrieve the Bibtex-entries of the citations found
            Collection<BibtexEntry> entries = getBibtexEntries(ticket, citations);
            
            if (entries.size() == 0){
                status.showMessage(Globals.lang("No entries found for the search string '%0'",
                        query),
                        Globals.lang("Search JSTOR"), JOptionPane.INFORMATION_MESSAGE);
                return false;
            }
            
            for (BibtexEntry entry : entries){
                dialog.addEntry(entry);
            }
            return true;
        } catch (IOException e) {
            status.showMessage(Globals.lang("Error while fetching from JSTOR") + ": " + e.getMessage());
        }
        return false;
    }

    /**
     * Given a ticket an a list of citations, retrieve BibtexEntries from JStor
     * 
     * @param ticket
     *            A valid ticket as returned by openTicket()
     * @param citations
     *            A list of citations as returned by getCitations()
     * @return A collection of BibtexEntries parsed from the bibtex returned by
     *         JStor.
     * @throws IOException
     *             Most probably related to a problem connecting to JStor.
     */
    protected Collection<BibtexEntry> getBibtexEntries(String ticket, String citations)
        throws IOException {
        try {
            URL url = new URL(URL_BIBTEX);
            URLConnection conn = url.openConnection();
            conn.setRequestProperty("Cookie", ticket + "; " + citations);
            conn.connect();

            BibtexParser parser = new BibtexParser(new BufferedReader(new InputStreamReader(conn
                .getInputStream())));
            return parser.parse().getDatabase().getEntries();
        } catch (MalformedURLException e) {
            // Propagate...
            throw new RuntimeException(e);
        }
    }

    /**
     * 
     * @return a Jstor ticket ID
     * @throws IOException
     */
    protected String openTicket() throws IOException {
        URL url = new URL(URL_TICKET);
        URLConnection conn = url.openConnection();
        return getCookie(COOKIE_TICKET, conn);
    }

    /**
     * requires a valid JStor Ticket ID
     * 
     * @param query
     *            The search term to query JStor for.
     * @param ticket
     *            JStor ticket
     * @return cookie value of the key JSTORFetcher.COOKIE_CITATIONS. null if
     *         search is empty or ticket is invalid
     * @throws IOException
     */
    protected String getCitations(String ticket, String query) throws IOException {
        String urlQuery;
        try {
            urlQuery = "http://www.jstor.org/search/BasicResults?hp=" + MAX_CITATIONS +
                "&si=1&gw=jtx&jtxsi=1&jcpsi=1&artsi=1&Query=" + URLEncoder.encode(query, "UTF-8") +
                "&wc=on&citationAction=saveAll";
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        URL url = new URL(urlQuery);
        URLConnection conn = url.openConnection();
        conn.setRequestProperty("Cookie", ticket);
        return getCookie(COOKIE_CITATIONS, conn);
    }

    /**
     * evaluates the 'Set-Cookie'-Header of a HTTP response
     * 
     * @param name
     *            key of a cookie value
     * @param conn
     *            URLConnection
     * @return cookie value referenced by the key. null if key not found
     * @throws IOException
     */
    public static String getCookie(String name, URLConnection conn) throws IOException {

        for (int i = 0;; i++) {
            String headerName = conn.getHeaderFieldKey(i);
            String headerValue = conn.getHeaderField(i);

            if (headerName == null && headerValue == null) {
                // No more headers
                break;
            }
            if (headerName != null && headerName.equals("Set-Cookie")) {
                if (headerValue.startsWith(name)) {
                    // several key-value-pairs are separated by ';'
                    StringTokenizer st = new StringTokenizer(headerValue, "; ");
                    while (st.hasMoreElements()) {
                        String token = st.nextToken();
                        if (token.startsWith(name)) {
                            return token;
                        }
                    }
                }
            }

        }
        return null;
    }

}
