package org.limewire.core.impl;

import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.limewire.bittorrent.Torrent;
import org.limewire.core.api.callback.GuiCallback;
import org.limewire.core.api.callback.GuiCallbackService;
import org.limewire.core.api.download.DownloadAction;
import org.limewire.core.api.download.DownloadException;
import org.limewire.core.impl.download.DownloadListener;
import org.limewire.core.impl.download.DownloadListenerList;
import org.limewire.core.impl.magnet.MagnetLinkImpl;
import org.limewire.core.impl.monitor.IncomingSearchListener;
import org.limewire.core.impl.monitor.IncomingSearchListenerList;
import org.limewire.core.impl.search.QueryReplyListener;
import org.limewire.core.impl.search.QueryReplyListenerList;
import org.limewire.core.impl.upload.UploadListener;
import org.limewire.core.impl.upload.UploadListenerList;
import org.limewire.i18n.I18nMarker;
import org.limewire.io.GUID;
import org.limewire.io.IpPort;
import org.limewire.service.ErrorService;
import org.limewire.service.MessageService;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.limegroup.gnutella.ActivityCallback;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.Downloader;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.Uploader;
import com.limegroup.gnutella.browser.MagnetOptions;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;

/**
 * An implementation of the UI callback to handle notifications about 
 * asynchronous backend events.
 */
@Singleton
class GlueActivityCallback implements ActivityCallback, QueryReplyListenerList,
        DownloadListenerList, UploadListenerList, 
        IncomingSearchListenerList, GuiCallbackService {

    private final SortedMap<byte[], List<QueryReplyListener>> queryReplyListeners;

    private final List<DownloadListener> downloadListeners = new CopyOnWriteArrayList<DownloadListener>();
    
    private final List<UploadListener> uploadListeners = new CopyOnWriteArrayList<UploadListener>();

    private final List<IncomingSearchListener> monitorListeners = new CopyOnWriteArrayList<IncomingSearchListener>();

    private final DownloadManager downloadManager;
    
    private GuiCallback guiCallback = null;
    
    @Inject
    public GlueActivityCallback(DownloadManager downloadManager) {
        this.downloadManager = downloadManager;
        queryReplyListeners = new ConcurrentSkipListMap<byte[], List<QueryReplyListener>>(
                GUID.GUID_BYTE_COMPARATOR);
    }

    @Override
    public void addQueryReplyListener(byte[] guid, QueryReplyListener listener) {
        synchronized (queryReplyListeners) {
            List<QueryReplyListener> listeners = queryReplyListeners.get(guid);
            if (listeners == null) {
                listeners = new CopyOnWriteArrayList<QueryReplyListener>();
                queryReplyListeners.put(guid, listeners);
            }
            listeners.add(listener);
        }
    }

    @Override
    public void removeQueryReplyListener(byte[] guid, QueryReplyListener listener) {
        synchronized (queryReplyListeners) {
            List<QueryReplyListener> listeners = queryReplyListeners.get(guid);
            if (listeners != null) {
                listeners.remove(listener);
                if (listeners.isEmpty()) {
                    queryReplyListeners.remove(guid);
                }
            }
        }
    }

    @Override
    public void addDownloadListener(DownloadListener listener) {
        downloadListeners.add(listener);
    }

    @Override
    public void removeDownloadListener(DownloadListener listener) {
        downloadListeners.remove(listener);
    }

    @Override
    public void handleMagnets(MagnetOptions[] magnets) {
        if(guiCallback != null) {
            for(MagnetOptions magnetOption : magnets) {
                guiCallback.handleMagnet(new MagnetLinkImpl(magnetOption));
            }
        }
    }

    @Override
    public void handleQueryResult(RemoteFileDesc rfd, QueryReply queryReply,
            Set<? extends IpPort> locs) {
        List<QueryReplyListener> listeners = queryReplyListeners.get(queryReply.getGUID());
        if (listeners != null) {
            for (QueryReplyListener listener : listeners) {
                listener.handleQueryReply(rfd, queryReply, locs);
            }
        }
    }

    // TODO: address and port - are ignored
    @Override
    public void handleQuery(QueryRequest query, String address, int port) {
        for (IncomingSearchListener listener : monitorListeners) {
            listener.handleQueryString(query.getQuery());
        }
    }

    public void handleSharedFileUpdate(File file) {
        //TODO PJV this looks like it is supposed to update the gui with the meta data changes about how many hits this file received in searches and how many times it has been uploaded.
        //Can we get rid of this now and use a property listener when we need this data again? could fireProperty events when incrementHitcount etc. are called
    }

    @Override
    public void handleTorrent(final File torrentFile) {
        if(torrentFile != null && torrentFile.exists() && torrentFile.length() > 0) {
            try {
                downloadManager.downloadTorrent(torrentFile, null, false);
            } catch (DownloadException e) {
                handleDownloadException(new DownloadAction() {
                  @Override
                    public void download(File saveDirectory, boolean overwrite) throws DownloadException {
                          downloadManager.downloadTorrent(torrentFile, saveDirectory, overwrite);
                    }

                @Override
                public void downloadCanceled(DownloadException ignored) {
                    //nothing to do
                }
                
                },e,false);
            }
        }
    }

    @Override
    public void installationCorrupted() {
        MessageService.showError(I18nMarker.marktr("<html><b>WireShare may have been corrupted by a virus or trojan.</b></html>"));
    }

    @Override
    public boolean isQueryAlive(GUID guid) {
        return queryReplyListeners.containsKey(guid.bytes());
    }

    @Override
    public void addUpload(Uploader u) {
        for (UploadListener listener : uploadListeners) {
            listener.uploadAdded(u);
        }
    }
    
    @Override
    public void uploadComplete(Uploader u) {
        for (UploadListener listener : uploadListeners) {
            listener.uploadComplete(u);
        }
    }

    @Override
    public void restoreApplication() {
        if(guiCallback != null) {
            guiCallback.restoreApplication();
        }
    }

    @Override
    public String translate(String s) {
        if(guiCallback != null) {
            return guiCallback.translate(s);
        }
        
        return s;
    }

    @Override
    public void uploadsComplete() {
        for (UploadListener listener : uploadListeners) {
            listener.uploadsCompleted();
        }
    }

    @Override
    public void addDownload(Downloader d) {
        for (DownloadListener listener : downloadListeners) {
            listener.downloadAdded(d);
        }
    }
    
    @Override
    public void downloadsComplete() {
        for (DownloadListener listener : downloadListeners) {
            listener.downloadsCompleted();
        }
    }
    
    @Override
    public void removeDownload(Downloader d) {
        for (DownloadListener listener : downloadListeners) {
            listener.downloadRemoved(d);
        }
    }
    
    public void setGuiCallback(GuiCallback guiCallback) {
        this.guiCallback = guiCallback;
    }

    @Override
    public void handleDownloadException(DownloadAction downLoadAction,
            DownloadException e, boolean supportsNewSaveDir) {
        if(guiCallback != null) {
            guiCallback.handleDownloadException(downLoadAction, e, supportsNewSaveDir);
        } else {
            ErrorService.error(e, "Error handling DownloadException. GuiCallBack not yet initialized.");
        }
    }

    @Override
    public void addUploadListener(UploadListener listener) {
        uploadListeners.add(listener);
    }

    @Override
    public void removeUploadListener(UploadListener listener) {
        uploadListeners.remove(listener);
    }

    @Override
    public void addIncomingSearchListener(IncomingSearchListener listener) {
        monitorListeners.add(listener);
    }

    @Override
    public void removeIncomingSearchListener(IncomingSearchListener listener) {
        monitorListeners.remove(listener);
    }

    @Override
    public boolean promptTorrentFilePriorities(Torrent torrent) {
        if(guiCallback != null && torrent.hasMetaData()) {
            return guiCallback.promptTorrentFilePriorities(torrent);
        }
        return true;
    }

    /**
     * Asks the user whether to continue with a torrent download that contains
     * files with banned extensions.
     * @return true if the download should continue.
     */
    @Override
    public boolean promptAboutTorrentWithBannedExtensions(Torrent torrent, Set<String> bannedExtensions) {
        if(guiCallback != null)
            return guiCallback.promptAboutTorrentWithBannedExtensions(torrent, bannedExtensions);
        return true;
    }
    
    /**
     * Asks the user whether to continue with a torrent if scanning the torrent file failed.
     * @return true if the download should continue.
     */
    @Override
    public boolean promptAboutTorrentDownloadWithFailedScan() {
        if (guiCallback != null)
            return guiCallback.promptAboutTorrentDownloadWithFailedScan();
        return true;
    }
}
