/*
 This file is part of the BlueJ program. 
 Copyright (C) 2013  Michael Kolling and John Rosenberg 
 
 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. 
 
 This file is subject to the Classpath exception as provided in the  
 LICENSE.txt file that accompanied this code.
 */
package bluej.collect;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

/**
 * This class that handles submitting compilation data to the remote server.
 * 
 * The class has nothing to do with collecting the data, and deliberately
 * does not depend on any other BlueJ classes.  Package-visible.
 * 
 * @author Davin McCall
 */
class DataSubmitter
{
    private static final String submitUrl = //"http://blackbox.bluej.org/master_events";
        //For testing:
            "http://localhost/master_events"; 

    
    private static volatile boolean givenUp = false;
    
    /**
     * isRunning is only touched while synchonized on queue
     */
    private static boolean isRunning = false;
    
    private static List<Event> queue = new LinkedList<Event>();
    
    private static int sequenceNum;

    /**
     * The versions of the files as we have last sent them to the server.
     * 
     * Should only be accessed by the postData method, which is running on
     * the event-sending thread
     */
    private static Map<FileKey, List<String> > fileVersions = new HashMap<FileKey, List<String> >();
    
    /**
     * Submit data to be posted to the server. The data is added to a queue which is processed by
     * another thread.
     * 
     * Package-visible, only used by DataCollector
     */
    static void submitEvent(Event evt)
    {
        //This thread only reads the boolean, and is an optimisation:
        if (givenUp)
            return;
        
        synchronized (queue) {
            queue.add(evt);
            
            if (! isRunning) {
                new Thread() {
                    public void run()
                    {
                        processQueue();
                    }
                }.start();
                isRunning = true;
            }
        }
    }
    
    /**
     * Process the queue of items to be posted to the server.
     */
    private static void processQueue()
    {
        while (true) {
            Event evt;
            synchronized (queue) {
                if (queue.isEmpty()) {
                    isRunning = false;
                    queue.notifyAll(); // in case anyone is waiting for us to finish
                    return;
                }
                evt = queue.remove(0);
            }
            

            if (!givenUp)
            {
                givenUp = !postData(evt);
            }
        }
    }
    
    /**
     * Actually post the data to the server.
     * 
     * Returns false if there was an error.
     */
    private static boolean postData(Event evt)
    {   
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params, 10000);
        HttpConnectionParams.setSoTimeout(params, 10000);
        HttpClient client = new DefaultHttpClient(params);
        
        try {
            HttpPost post = new HttpPost(submitUrl);

            
            /*
            if (errFilePath != null) {
                mpe.addPart("filepath", new StringBody(errFilePath, utf8));
            }
            if (errMsg != null) {
                mpe.addPart("errormsg", new StringBody(errMsg, utf8));
            }
            if (errline != 0) {
                mpe.addPart("errorline", new StringBody("" + errline, utf8));
            }
            
            int i = 0;
            for (SourceContent changedFile : changedFiles) {
                mpe.addPart("sourcefileName" + i, new StringBody(changedFile.getFilePath(), utf8));
                mpe.addPart("sourcefileContent" + i, new ByteArrayBody(changedFile.getFileContent(),
                        changedFile.getFilePath()));
            }
            */
            MultipartEntity mpe = evt.makeData(sequenceNum, fileVersions);
            if (mpe == null)
                return true; // nothing to send, no error
            //Only increment sequence number if we actually send data:
            sequenceNum += 1;
            post.setEntity(mpe);
            HttpResponse response = client.execute(post);
            
            for (Header h : response.getAllHeaders())
            {
                if ("X-Status".equals(h.getName()) && !"Created".equals(h.getValue()))
                {
                    // Temporary printing:
                    System.err.println("Problem response: " + mpe.toString() + " " + response.toString());
                    return false;
                }
            }
            
            if (response.getStatusLine().getStatusCode() != 200)
            {
                System.err.println("Problem code: " + response.getStatusLine().getStatusCode());
                return false;
            }
            
            evt.success(fileVersions);
                
            EntityUtils.consume(response.getEntity());
        }
        catch (ClientProtocolException cpe) {
            return false;
        }
        catch (IOException ioe) {
            //For now:
            ioe.printStackTrace();
            
            return false;
        }
        
        return true;
    }
    
    /**
     * Waits until all pending events have been sent to the server, or the timeout expires.  If events are still being added in parallel
     * to this call, there will be undefined behaviour.
     */
    public static void waitForQueueFlush(int maxMillis)
    {
        final long endTime = System.currentTimeMillis() + maxMillis; 
        
        try
        {
            synchronized (queue)
            {
                // Keep waiting if there is anything in the queue,
                // or the queue is empty but the submitter thread is still running.
                while (!queue.isEmpty() || isRunning)
                {
                    long waitTime = endTime - System.currentTimeMillis();
                    if (waitTime <= 0) {
                        break;
                    }
                    queue.wait(waitTime);
                }
            }
        }
        catch (InterruptedException e)
        {
            //Just finish anyway...
        }
    }

    public static void initSequence()
    {
        sequenceNum = 1; //Server relies on it starting at 1, do not change
        
    }
}
