package com.muwire.gui

import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.directories.WatchedDirectory
import com.muwire.core.messenger.UIFolderCreateEvent
import com.muwire.core.messenger.UIFolderDeleteEvent

import javax.swing.JTextField
import java.util.regex.Pattern

import static com.muwire.gui.Translator.trans
import griffon.core.GriffonApplication
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.crypto.DSAEngine
import net.i2p.data.Base64
import net.i2p.data.Signature

import java.awt.Desktop
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
import java.awt.event.ActionEvent
import java.nio.charset.StandardCharsets

import javax.annotation.Nonnull
import javax.inject.Inject
import javax.swing.JOptionPane
import javax.swing.JTable

import com.muwire.core.Constants
import com.muwire.core.Core
import com.muwire.core.InfoHash
import com.muwire.core.Persona
import com.muwire.core.SharedFile
import com.muwire.core.SplitPattern
import com.muwire.core.collections.FileCollection
import com.muwire.core.collections.UICollectionDeletedEvent
import com.muwire.core.download.Downloader
import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.filecert.UICreateCertificateEvent
import com.muwire.core.filefeeds.Feed
import com.muwire.core.filefeeds.FeedItem
import com.muwire.core.filefeeds.UIDownloadFeedItemEvent
import com.muwire.core.filefeeds.UIFeedConfigurationEvent
import com.muwire.core.filefeeds.UIFeedDeletedEvent
import com.muwire.core.filefeeds.UIFeedUpdateEvent
import com.muwire.core.filefeeds.UIFilePublishedEvent
import com.muwire.core.filefeeds.UIFileUnpublishedEvent
import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent
import com.muwire.core.trust.RemoteTrustList
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
import com.muwire.core.trust.TrustService.TrustEntry
import com.muwire.core.trust.TrustSubscriptionEvent
import com.muwire.core.upload.HashListUploader
import com.muwire.core.upload.Uploader
import com.muwire.core.util.DataUtil

@ArtifactProviderFor(GriffonController)
class MainFrameController {
    @Inject @Nonnull GriffonApplication application
    @MVCMember @Nonnull
    FactoryBuilderSupport builder

    @MVCMember @Nonnull
    MainFrameModel model
    @MVCMember @Nonnull
    MainFrameView view

    private volatile Core core

    @ControllerAction
    void search(ActionEvent evt) {
        if (evt?.getActionCommand() == null)
            return
        def cardsPanel = builder.getVariable("cards-panel")
        cardsPanel.getLayout().show(cardsPanel, "search window")

        def searchField = builder.getVariable("search-field") 
        def search = searchField.getSelectedItem()
        searchField.model.addElement(search)
        performSearch(search, null)
    }
    
    void repeatSearch(String terms, Integer tab, Boolean regex) {
        if (regex)
            terms = "/$terms/"
        performSearch(terms, tab)
    }
    
    private void performSearch(String search, Integer tab) {
        
        model.sessionRestored = true
        
        search = search.trim()
        if (search.length() == 0)
            return
        if (search.length() > 128) {
            try {
                Persona p = new Persona(new ByteArrayInputStream(Base64.decode(search)))
                String groupId = UUID.randomUUID().toString()
                def params = [:]
                params['host'] = p
                params['core'] = model.core
                mvcGroup.createMVCGroup("browse",groupId,params)
                return
            } catch (Exception notPersona) {
                search = search.substring(0,128)
            }
        }

        boolean hashSearch = false
        byte [] root = null
        if (search.length() == 44 && search.indexOf(" ") < 0) {
            try {
                root = Base64.decode(search)
                hashSearch = true
            } catch (Exception e) {
                // not a hash search
            }
        }
        
        boolean regexSearch = false
        if (search.length() > 1 && search.startsWith("/") && search.endsWith("/")) {
            search = search.substring(1, search.length() - 1)
            try {
                Pattern.compile(search)
                regexSearch = true
            } catch (Exception e) {
                // not a regex search
                JOptionPane.showMessageDialog(null, trans("NOT_A_REGEX", search),
                    trans("NOT_A_REGEX_TITLE"), JOptionPane.WARNING_MESSAGE);
                return
            }
        }
        
        def uuid = UUID.randomUUID()
        Map<String, Object> params = new HashMap<>()
        params["search-terms"] = search
        params["uuid"] = uuid.toString()
        params["core"] = core
        params["settings"] = view.settings
        params["tab"] = tab
        params["regex"] = regexSearch
        def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
        model.results[uuid.toString()] = group

        def searchEvent
        byte [] payload
        if (hashSearch) {
            searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true,
                compressedResults : true, persona : core.me, collections : core.muOptions.searchCollections)
            payload = root
        } else {
            String [] terms
            if (regexSearch) {
                terms = new String[] {search}
                payload = search.getBytes(StandardCharsets.UTF_8)
            } else {
                terms = SplitPattern.termify(search)
                payload = String.join(" ", terms).getBytes(StandardCharsets.UTF_8)
            }
            searchEvent = new SearchEvent(searchTerms : terms, uuid : uuid, oobInfohash: true,
            searchComments : core.muOptions.searchComments, compressedResults : true, persona : core.me,
            collections : core.muOptions.searchCollections, searchPaths: core.muOptions.searchPaths, regex: regexSearch)
        }
        boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop

        Signature sig = DSAEngine.getInstance().sign(payload, core.spk)

        long timestamp = System.currentTimeMillis()
        core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop, local: true,
        replyTo: core.me.destination, receivedOn: core.me.destination,
        originator : core.me, sig : sig.data, queryTime : timestamp, sig2 : DataUtil.signUUID(uuid, timestamp, core.spk)))

    }

    void search(String infoHash, String tabTitle) {
        def cardsPanel = builder.getVariable("cards-panel")
        cardsPanel.getLayout().show(cardsPanel, "search window")
        def uuid = UUID.randomUUID()
        Map<String, Object> params = new HashMap<>()
        params["search-terms"] = tabTitle
        params["uuid"] = uuid.toString()
        params["core"] = core
        params["settings"] = view.settings
        def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
        model.results[uuid.toString()] = group

        byte [] infoHashBytes = Base64.decode(infoHash)
        Signature sig = DSAEngine.getInstance().sign(infoHashBytes, core.spk)
        long timestamp = System.currentTimeMillis()
        byte [] sig2 = DataUtil.signUUID(uuid, timestamp, core.spk)
        
        def searchEvent = new SearchEvent(searchHash : Base64.decode(infoHash), uuid:uuid,
            oobInfohash: true, persona : core.me)
        core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true, local: true,
            replyTo: core.me.destination, receivedOn: core.me.destination,
            originator : core.me, sig : sig.data, queryTime : timestamp, sig2 : sig2))
    }
    
    private List<Downloader> selectedDownloads() {
        int [] rows = view.selectedDownloaderRows()
        if (rows.length == 0)
            return Collections.emptyList()
        List<Downloader> rv = []
        for (int row : rows)
            rv << model.downloads[row].downloader
        rv
    }
    
    @ControllerAction
    void trustPersonaFromSearch() {
        int selected = builder.getVariable("searches-table").getSelectedRow()
        if (selected < 0)
            return
        String reason = JOptionPane.showInputDialog(trans("ENTER_REASON_OPTIONAL"))
        Persona p = model.searches[selected].originator
        core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED, reason : reason) )
    }

    @ControllerAction
    void distrustPersonaFromSearch() {
        int selected = builder.getVariable("searches-table").getSelectedRow()
        if (selected < 0)
            return
        String reason = JOptionPane.showInputDialog(trans("ENTER_REASON_OPTIONAL"))
        Persona p = model.searches[selected].originator
        core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED, reason : reason) )
    }

    @ControllerAction
    void copyDownloadHash() {
        List<Downloader> downloaders = selectedDownloads()
        if (downloaders.size() != 1)
            return
        def download = downloaders.first()
        CopyPasteSupport.copyToClipboard(Base64.encode(download.getInfoHash().getRoot()))
    }
    
    @ControllerAction
    void cancel() {
        for (Downloader downloader : selectedDownloads()) {
            downloader.cancel()
            model.downloadInfoHashes.remove(downloader.getInfoHash())
            core.eventBus.publish(new UIDownloadCancelledEvent(downloader: downloader))
        }
    }

    @ControllerAction
    void resume() {
        for (Downloader downloader : selectedDownloads()) {
            downloader.resume()
            core.eventBus.publish(new UIDownloadResumedEvent())
        }
    }

    @ControllerAction
    void pause() {
        for (Downloader downloader : selectedDownloads()) {
            downloader.pause()
            core.eventBus.publish(new UIDownloadPausedEvent())
        }
    }
    
    @ControllerAction
    void preview() {
        def downloads = selectedDownloads()
        if (downloads.size() != 1)
            return
        def downloader = downloads.get(0)
        def params = [:]
        params['downloader'] = downloader
        mvcGroup.createMVCGroup("download-preview", params)
    }
    
    @ControllerAction
    void open() {
        List<SharedFile> selected = view.selectedSharedFiles()
        if (selected == null || selected.size() != 1)
            return
        if (!view.selectedFolders().isEmpty())
            return
        SharedFile sf = selected[0]
        Desktop.getDesktop().open(sf.file)
    }

    @ControllerAction
    void clear() {
        model.downloads.removeAll { 
            def state = it.downloader.getCurrentState()
            state == Downloader.DownloadState.CANCELLED ||
                state == Downloader.DownloadState.FINISHED ||
                state == Downloader.DownloadState.HOPELESS
        }
        
         model.clearButtonEnabled = false

    }

    @ControllerAction
    void openContainingFolderFromDownload() {
        def downloads = selectedDownloads()
        if (downloads.size() != 1)
            return
        def downloader = downloads.get(0)

        try {
            Desktop.getDesktop().open(downloader.file.getParentFile())
        } catch (Exception ignored) {}
    }
    
    @ControllerAction
    void addContact() {
        def params = [:]
        params.core = core
        mvcGroup.createMVCGroup("add-contact", params).destroy()
    }
    
    @ControllerAction
    void removeContact() {
        markTrust(TrustLevel.NEUTRAL)
    }

    private void markTrust(TrustLevel level) {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        String reason = null
        if (level != TrustLevel.NEUTRAL)
            reason = JOptionPane.showInputDialog(trans("ENTER_REASON_OPTIONAL"))
        builder.getVariable("contacts-table").model.fireTableDataChanged()
        core.eventBus.publish(new TrustEvent(persona : model.contacts[row].persona, level : level, reason : reason))
    }

    @ControllerAction
    void markTrusted() {
        markTrust(TrustLevel.TRUSTED)
        model.markTrustedButtonEnabled = false
    }

    @ControllerAction
    void markDistrusted() {
        markTrust(TrustLevel.DISTRUSTED)
        model.subscribeButtonEnabled = false
        model.markDistrustedButtonEnabled = false
    }

    @ControllerAction
    void subscribe() {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        Persona p = model.contacts[row].persona
        core.muOptions.trustSubscriptions.add(p)
        saveMuWireSettings()
        core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true))
        model.subscribeButtonEnabled = false
        model.markDistrustedButtonEnabled = false
        model.removeContactButtonEnabled = false
    }
    
    @ControllerAction
    void subscribeToFeed() {
        String address = JOptionPane.showInputDialog(trans("COPY_PASTE_FEED_ID"))
        if (address == null)
            return
        Persona p
        try {
            p = new Persona(new ByteArrayInputStream(Base64.decode(address)))
        } catch (Exception bad) {
            JOptionPane.showMessageDialog(null, trans("ADD_CONTACT_INVALID_ID_TITLE"), trans("ADD_CONTACT_INVALID_ID_TITLE"), JOptionPane.ERROR_MESSAGE)
            return
        }

        Feed feed = new Feed(p)
        feed.setAutoDownload(core.muOptions.defaultFeedAutoDownload)
        feed.setSequential(core.muOptions.defaultFeedSequential)
        feed.setItemsToKeep(core.muOptions.defaultFeedItemsToKeep)
        feed.setUpdateInterval(core.muOptions.defaultFeedUpdateInterval)

        core.eventBus.publish(new UIFeedConfigurationEvent(feed : feed, newFeed: true))
        Thread.sleep(10)
        view.refreshFeeds()
    }
    
    @ControllerAction
    void showMyFeed() {
        def params = [:]
        params['core'] = model.core
        mvcGroup.createMVCGroup("my-feed", params).destroy()
    }

    @ControllerAction
    void review() {
        RemoteTrustList list = getSelectedTrustList()
        if (list == null)
            return
        Map<String,Object> env = new HashMap<>()
        env["trustList"] = list
        env["trustService"] = core.trustService
        env["eventBus"] = core.eventBus
        mvcGroup.createMVCGroup("trust-list", env)

    }

    @ControllerAction
    void update() {
        RemoteTrustList list = getSelectedTrustList()
        if (list == null)
            return
        core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : true))
    }

    @ControllerAction
    void unsubscribe() {
        RemoteTrustList list = getSelectedTrustList()
        if (list == null)
            return
        core.muOptions.trustSubscriptions.remove(list.persona)
        saveMuWireSettings()
        model.subscriptions.remove(list)
        JTable table = builder.getVariable("subscription-table")
        table.model.fireTableDataChanged()
        core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : false))
    }

    private RemoteTrustList getSelectedTrustList() {
        int row = view.getSelectedContactSubscriptionTableRow()
        if (row < 0)
            return null
        model.subscriptions[row]
    }
    
    @ControllerAction
    void browseFromTrusted() {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        Persona p = model.contacts[row].persona
        
        String groupId = UUID.randomUUID().toString()
        def params = [:]
        params['host'] = p
        params['core'] = model.core
        mvcGroup.createMVCGroup("browse",groupId,params)
        view.showSearchWindow.call()
    }
    
    @ControllerAction
    void browseCollectionsFromTrusted() {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        Persona p = model.contacts[row].persona
        
        UUID uuid = UUID.randomUUID()
        def params = [:]
        params.fileName = p.getHumanReadableName()
        params.eventBus = core.eventBus
        params.everything = true
        params.uuid = uuid
        params.host = p
        mvcGroup.createMVCGroup("collection-tab", uuid.toString(), params)
        view.showSearchWindow.call()
    }
    
    @ControllerAction
    void browseFromUpload(Uploader u) {
        Persona p = u.getDownloaderPersona()

        String groupId = UUID.randomUUID().toString()
        def params = [:]
        params['host'] = p
        params['core'] = model.core
        mvcGroup.createMVCGroup("browse",groupId,params)
        view.showSearchWindow.call()
    }
    
    @ControllerAction
    void browseCollectionsFromUpload(Uploader u) {
        Persona p = u.getDownloaderPersona()
        
        UUID uuid = UUID.randomUUID()
        def params = [:]
        params.fileName = p.getHumanReadableName()
        params.eventBus = core.eventBus
        params.host = p
        params.uuid = uuid
        params.everything = true
        
        mvcGroup.createMVCGroup("collection-tab", uuid.toString(), params)
        view.showSearchWindow.call()
    }
    
    @ControllerAction
    void subscribeFromUpload(Uploader u) {
        Persona p = u.getDownloaderPersona()
        
        Feed feed = new Feed(p)
        feed.setAutoDownload(core.muOptions.defaultFeedAutoDownload)
        feed.setSequential(core.muOptions.defaultFeedSequential)
        feed.setItemsToKeep(core.muOptions.defaultFeedItemsToKeep)
        feed.setUpdateInterval(core.muOptions.defaultFeedUpdateInterval)
        
        core.eventBus.publish(new UIFeedConfigurationEvent(feed : feed, newFeed : true))
        view.showFeedsWindow.call()
    }
    
    @ControllerAction
    void chatFromUpload(Uploader u) {
        Persona p = u.getDownloaderPersona()
        startChat(p)
        view.showChatWindow.call()
    }

    void unshareSelectedFile() {
        def sfs = view.selectedSharedFiles()
        Set<File> folders = view.selectedFolders()
        if (sfs == null && folders.isEmpty()) 
            return
        if (sfs == null)
            sfs = Collections.emptyList()
        
        List<SharedFile> toUnshare = new ArrayList<>()
        sfs.each { SharedFile sf ->
            
            if (view.settings.collectionWarning) {
                Set<InfoHash> collectionsInfoHashes = core.collectionManager.collectionsForFile(sf.rootInfoHash)
                if (!collectionsInfoHashes.isEmpty()) {
                    String[] affected = collectionsInfoHashes.collect({core.collectionManager.getByInfoHash(it)}).collect{it.name}.toArray(new String[0])

                    boolean [] answer = new boolean[1]
                    def props = [:]
                    props.collections = affected
                    props.answer = answer
                    props.fileName = sf.file.getName()
                    props.settings = view.settings
                    props.home = core.home
                    def mvc = mvcGroup.createMVCGroup("collection-warning", props)
                    mvc.destroy()
                    if (!answer[0]) {
                        File f = sf.getFile()
                        while(true) {
                            File parent = f.getParentFile()
                            if (parent == null)
                                break
                            folders.remove(parent)
                            f = parent
                        }
                        return
                    }
                }
            }
            toUnshare.add(sf)
        }
        if (!folders.isEmpty())
            core.eventBus.publish(new DirectoryUnsharedEvent(directories: folders.toArray(new File[0]), deleted: false))
        if (!toUnshare.isEmpty())
            core.eventBus.publish(new FileUnsharedEvent(unsharedFiles : toUnshare.toArray(new SharedFile[0])))
    }
    
    @ControllerAction
    void addComment() {
        def selectedFiles = view.selectedSharedFiles()
        if (selectedFiles == null || selectedFiles.isEmpty())
            return
        
        Map<String, Object> params = new HashMap<>()
        params['selectedFiles'] = selectedFiles
        params['core'] = core
        mvcGroup.createMVCGroup("add-comment", "Add Comment", params).destroy()
    }
    
    @ControllerAction
    void clearUploads() {
        model.uploads.removeAll { it.finished }
    }
    
    @ControllerAction
    void showInLibrary(Uploader uploader) {
        SharedFile sf = null
        if (uploader instanceof HashListUploader) {
            InfoHash infoHash = uploader.infoHash
            SharedFile[] sfs = core.fileManager.rootToFiles.get(infoHash)
            if (sfs != null  && sfs.length > 0)
                sf = sfs[0]
        } else {
            File f = uploader.file
            sf = core.fileManager.fileToSharedFile.get(f)
        }
        
        if (sf == null)
            return // can happen if user un-shared

        view.focusOnSharedFile(sf)
    }
    
    @ControllerAction
    void restoreSession() {
        model.sessionRestored = true
        view.settings.openTabs.each { 
            performSearch(it, null)
        }
    }
    
    @ControllerAction
    void issueCertificate() {
        if (view.settings.certificateWarning) {
            def params = [:]
            params['settings'] = view.settings
            params['home'] = core.home
            mvcGroup.createMVCGroup("certificate-warning", params).destroy()
        } else {
            int count = 0
            view.selectedSharedFiles().each {
                count++
                core.eventBus.publish(new UICreateCertificateEvent(sharedFile : it))
            }
            if (count > 1)
                JOptionPane.showMessageDialog(null, trans("CERTIFICATES_ISSUED"))
            else if (count == 1)
                JOptionPane.showMessageDialog(null, trans("CERTIFICATE_ISSUED"))
        }
    }
    
    @ControllerAction
    void showFileDetails() {
        def selected = view.selectedSharedFiles()
        if (selected == null || selected.size() != 1) {
            JOptionPane.showMessageDialog(null, trans("PLEASE_SELECT_ONE_FILE_DETAILS"))
            return
        }
        def params = [:]
        params['sf'] = selected[0]
        params['core'] = core
        mvcGroup.createMVCGroup("shared-file", params)
    }
    
    @ControllerAction
    void configureFolder() {
        Set<File> selectedFolders = view.selectedFolders()
        if (selectedFolders.size() != 1)
            return
        def file = selectedFolders.first()
        def wdm = model.core.getWatchedDirectoryManager()
        if (!wdm.isWatched(file))
            return

        WatchedDirectory wd = wdm.getDirectory(file)
        
        def props = [:]
        props.core = model.core
        props.directory = wd
        mvcGroup.createMVCGroup("watched-directory", UUID.randomUUID().toString(), props)
        
    }
    
    @ControllerAction
    void openContainingFolder() {
        def selected = view.selectedSharedFiles()
        if (selected == null || selected.size() != 1 || !view.selectedFolders().isEmpty()) {
            JOptionPane.showMessageDialog(null, trans("PLEASE_SELECT_ONE_FILE_FOLDER"))
            return
        }
        
        try {
            Desktop.getDesktop().open(selected[0].file.getParentFile())
        } catch (Exception ignored) {}
    }
    
    @ControllerAction
    void filterLibrary() {
        JTextField field = builder.getVariable("library-filter-textfield")
        String filter = field.getText()
        if (filter == null)
            return
        filter = filter.strip().replaceAll(SplitPattern.SPLIT_PATTERN," ").toLowerCase()
        String [] split = filter.split(" ")
        def hs = new HashSet()
        split.each {if (it.length() > 0) hs << it}
        model.filter = hs.toArray(new String[0])
        model.filterLibrary()
    }
    
    @ControllerAction
    void clearLibraryFilter() {
        model.clearFilterActionEnabled = false
        model.filter = null
        model.filterLibrary()
    }
    
    @ControllerAction
    void startChatServer() {
        model.core.chatServer.start()
        model.chatServerRunning = true
        
        if (!mvcGroup.getChildrenGroups().containsKey("local-chat-server")) {
            def params = [:]
            params['core'] = model.core
            params['host'] = model.core.me
            params['chatNotificator'] = view.chatNotificator
            mvcGroup.createMVCGroup("chat-server","local-chat-server", params)
        }
    }
    
    @ControllerAction
    void stopChatServer() {
        model.core.chatServer.stop()
        model.chatServerRunning = false
    }

    @ControllerAction
    void connectChatServer() {
        String address = JOptionPane.showInputDialog(trans("COPY_PASTE_SERVER_ADDRESS"))
        if (address == null)
            return
        Persona p
        try {
            p = new Persona(new ByteArrayInputStream(Base64.decode(address)))
        } catch (Exception bad) {
            JOptionPane.showMessageDialog(null, trans("INVALID_SERVER_ADDRESS"), trans("INVALID_SERVER_ADDRESS"), JOptionPane.ERROR_MESSAGE)
            return
        }
     
        startChat(p)   
    }
    
    @ControllerAction
    void copyShort() {
        copy(model.core.me.getHumanReadableName())
    }
    
    @ControllerAction
    void copyFull() {
        copy(model.core.me.toBase64())
    }
    
    private void copy(String s) {
        StringSelection selection = new StringSelection(s)
        def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
        clipboard.setContents(selection, null)
    }
    
    @ControllerAction
    void publish() {
        def selectedFiles = view.selectedSharedFiles()
        if (selectedFiles == null || selectedFiles.isEmpty())
            return
        
        def params = [:]
        params.core = model.core
        params.requested = selectedFiles
        mvcGroup.createMVCGroup("publish-preview",params).destroy()
        
        view.refreshSharedFiles()
    }
    
    @ControllerAction
    void updateFileFeed() {
        Feed feed = view.selectedFeed()
        if (feed == null)
            return
        model.core.eventBus.publish(new UIFeedUpdateEvent(host: feed.getPublisher()))
    }
    
    @ControllerAction
    void unsubscribeFileFeed() {
        Feed feed = view.selectedFeed()
        if (feed == null)
            return
        model.core.eventBus.publish(new UIFeedDeletedEvent(host : feed.getPublisher()))
        runInsideUIAsync {
            model.feeds.remove(feed)
            model.feedItems.clear()
            view.refreshFeeds()
        }
    }
    
    @ControllerAction
    void configureFileFeed() {
        Feed feed = view.selectedFeed()
        if (feed == null)
            return
        
        def params = [:]
        params['core'] = core
        params['feed'] = feed
        mvcGroup.createMVCGroup("feed-configuration", params)
    }
    
    @ControllerAction
    void downloadFeedItem() {
        List<FeedItem> items = view.selectedFeedItems()
        if (items == null || items.isEmpty())
            return
        Feed f = model.core.getFeedManager().getFeed(items.get(0).getPublisher())
        items.each { 
            if (!model.canDownload(it.getInfoHash()))
                return
            File target = new File(application.context.get("muwire-settings").downloadLocation, it.getName())
            model.core.eventBus.publish(new UIDownloadFeedItemEvent(item : it, target : target, sequential : f.isSequential()))
        }
        view.showDownloadsWindow.call()
    }
    
    @ControllerAction
    void viewFeedItemComment() {
        List<FeedItem> items = view.selectedFeedItems()
        if (items == null || items.size() != 1)
            return
        FeedItem item = items.get(0)
        
        String groupId = Base64.encode(item.getInfoHash().getRoot())
        Map<String, Object> params = new HashMap<>()
        params['text'] = DataUtil.readi18nString(Base64.decode(item.getComment()))
        params['name'] = item.getName()
        
        mvcGroup.createMVCGroup("show-comment", groupId, params).destroy()
    }
    
    @ControllerAction
    void viewFeedItemCertificates() {
        List<FeedItem> items = view.selectedFeedItems()
        if (items == null || items.size() != 1)
            return
        FeedItem item = items.get(0)
        
        def params = [:]
        params['core'] = core
        params['host'] = item.getPublisher()
        params['infoHash'] = item.getInfoHash()
        params['name'] = item.getName()
        mvcGroup.createMVCGroup("fetch-certificates", params)
    }
    
    @ControllerAction
    void collection() {
        def files = view.selectedSharedFiles()

        if (files != null && files.size() > Constants.COLLECTION_MAX_ITEMS) {
            JOptionPane.showMessageDialog(null, trans("CREATE_COLLECTION_MAX_FILES", Constants.COLLECTION_MAX_ITEMS, files.size()),
                trans("CREATE_COLLECTION_MAX_FILES_TITLE"), JOptionPane.WARNING_MESSAGE)
            return
        }
        if (files == null)
            files = new ArrayList<>()        
        def params = [:]
        params['files'] = files
        params['spk'] = model.core.spk
        params['me'] = model.core.me
        params['eventBus'] = model.core.eventBus
        
        mvcGroup.createMVCGroup("collection-wizard", UUID.randomUUID().toString(), params)
    }
    
    @ControllerAction
    void viewCollectionComment() {
        int row = view.selectedCollectionRow()
        if (row < 0)
            return
        FileCollection collection = model.localCollections.get(row)

        def params = [:]
        params['text'] = collection.comment
        mvcGroup.createMVCGroup("show-comment", params).destroy()
    }
    
    @ControllerAction
    void copyCollectionHash() {
        int row = view.selectedCollectionRow()
        if (row < 0)
            return
        FileCollection collection = model.localCollections.get(row)
        
        String b64 = Base64.encode(collection.getInfoHash().getRoot())
        
        StringSelection selection = new StringSelection(b64)
        def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
        clipboard.setContents(selection, null)
    }
    
    @ControllerAction
    void deleteCollection() {
        int row = view.selectedCollectionRow()
        if (row < 0)
            return
        FileCollection collection = model.localCollections.get(row)
        UICollectionDeletedEvent e = new UICollectionDeletedEvent(collection : collection)
        model.core.eventBus.publish(e)
        model.localCollections.remove(row)
        view.collectionsTable.model.fireTableDataChanged()
        model.collectionFiles.clear()
        view.collectionFilesTable.model.fireTableDataChanged()
    }
    
    @ControllerAction
    void viewItemComment() {
        int row = view.selectedItemRow()
        if (row < 0)
            return
        SharedFile sf = model.collectionFiles.get(row)
        
        def params = [:]
        params['text'] = DataUtil.readi18nString(Base64.decode(sf.getComment()))
        mvcGroup.createMVCGroup("show-comment", params).destroy()
    }
    
    @ControllerAction
    void showCollectionTool() {
        int row = view.selectedCollectionRow()
        if (row < 0)
            return
        FileCollection collection = model.localCollections.get(row)
        
        def params = [:]
        params['collection'] = collection
        mvcGroup.createMVCGroup("collections-tool", params)
    }
    
    @ControllerAction
    void messageComposeFromUpload(Uploader u) {
        Persona p = u.getDownloaderPersona()
        
        def params = [:]
        params.recipients = new HashSet<>(Collections.singletonList(p))
        params.core = core
        mvcGroup.createMVCGroup("new-message", UUID.randomUUID().toString(), params)
    }
    
    @ControllerAction
    void messageFromTrusted() {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        TrustEntry te = model.contacts[row]
        
        def params = [:]
        params.recipients = new HashSet<>(Collections.singletonList(te.persona))
        params.core = core
        mvcGroup.createMVCGroup("new-message", UUID.randomUUID().toString(), params)
    }
    
    @ControllerAction
    void copyIdFromUploads(Uploader u) {
        CopyPasteSupport.copyToClipboard(u.getDownloaderPersona().toBase64())
    }
    
    @ControllerAction
    void copyIdFromTrusted() {
        int row = view.getSelectedContactsTableRow()
        if (row < 0)
            return
        TrustEntry te = model.contacts.get(row)
        CopyPasteSupport.copyToClipboard(te.persona.toBase64())
    }
    
    @ControllerAction
    void copyIdFromFeed() {
        Feed feed = view.selectedFeed()
        if (feed == null)
            return
        CopyPasteSupport.copyToClipboard(feed.getPublisher().toBase64())
    }
    
    @ControllerAction
    void createMessageFolder() {
        String name = null
        while(name == null) {
            name = JOptionPane.showInputDialog(trans("FOLDER_ENTER_NAME"))
            if (name == null)
                return
            name = name.trim()
            if (name.length() == 0) {
                JOptionPane.showMessageDialog(null, trans("FOLDER_NAME_EMPTY"),
                    trans("FOLDER_NAME_EMPTY"), JOptionPane.WARNING_MESSAGE)
                name = null
                continue
            }
            if (model.messageFoldersMap.containsKey(name)) {
                JOptionPane.showMessageDialog(null, trans("FOLDER_ALREADY_EXISTS"),
                    trans("FOLDER_ALREADY_EXISTS"), JOptionPane.WARNING_MESSAGE)
                name = null
                continue
            }
        }
        
        def params = [:]
        params['core'] = core
        params['outgoing'] = false
        params['name'] = name
        String groupId = "folder-$name=" + UUID.randomUUID().toString()
        def group = application.mvcGroupManager.createMVCGroup('message-folder', groupId, params)
        view.addUserMessageFolder(group)

        UIFolderCreateEvent event = new UIFolderCreateEvent(name: name)
        core.eventBus.publish(event)
    }
    
    @ControllerAction
    void deleteMessageFolder() {
        
        def group = model.messageFoldersMap.get(model.folderIdx)
        if (group == null)
            return
        
        if (!group.model.messages.isEmpty()) {
            int result = JOptionPane.showConfirmDialog(null, trans("FOLDER_CONFIRM_DELETE"),
                    trans("FOLDER_CONFIRM_DELETE_TITLE"), JOptionPane.YES_NO_OPTION)
            if (result != JOptionPane.YES_OPTION)
                return
        }
        
        view.deleteUserMessageFolder(model.folderIdx)
        group.destroy()

        UIFolderDeleteEvent event = new UIFolderDeleteEvent(name: model.folderIdx)
        core.eventBus.publish(event)
    }
    
    void startChat(Persona p) {
        if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) {
            def params = [:]
            params['core'] = model.core
            params['host'] = p
            params['chatNotificator'] = view.chatNotificator
            mvcGroup.createMVCGroup("chat-server", p.getHumanReadableName(), params)
        } else 
            mvcGroup.getChildrenGroups().get(p.getHumanReadableName()).model.connect()
    }
    
    @ControllerAction
    void chatFavorites() {
        if (application.getMvcGroupManager().findGroup("chat-favorites") != null)
            return
        
        def params = [:]
        params.chatFavorites = view.chatFavorites
        mvcGroup.createMVCGroup("chat-favorites", params)
    }

    void saveMuWireSettings() {
        core.saveMuSettings()
    }

    void mvcGroupInit(Map<String, String> args) {
        application.addPropertyChangeListener("core", {e->
            core = e.getNewValue()
        })
    }
}
