/**************************************************************************
 OmegaT - Computer Assisted Translation (CAT) tool
          with fuzzy matching, translation memory, keyword search,
          glossaries, and translation leveraging into updated projects.

 Copyright (C) 2015 Thomas Cordonnier
               Home page: http://www.omegat.org/
               Support center: http://groups.yahoo.com/group/OmegaT/

 This file is part of OmegaT.

 OmegaT 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 3 of the License, or
 (at your option) any later version.

 OmegaT 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, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

package org.omegat.core.matching.external.rmi;

import org.omegat.core.data.*;
import org.omegat.core.Core;
import org.omegat.core.CoreEvents;

import org.omegat.core.matching.external.IEntryCursor;
import org.omegat.core.matching.external.ProjectMemory;
import org.omegat.core.events.IEntryEventListener;

import java.util.*;
import java.rmi.*;

/** 
 * A server which gives access to current project <br/>
 * Usage : OmegaT [--server-port=nnnn]? --server-object=any [project]
 **/
public class Server extends java.rmi.server.UnicastRemoteObject implements IDistantExternalMemory, IEntryEventListener {

    private Server() throws RemoteException {
        CoreEvents.registerEntryEventListener(this);
    }
    
    public static Server open(String portStr, String object) throws Exception {
        int port = 1099; if (portStr != null) port = Integer.parseInt(portStr);
        java.rmi.registry.LocateRegistry.createRegistry(port);
        System.err.println("Listening server " + port);
        
        if (! object.startsWith("//")) if (port != 1099) object = "//localhost:" + port + "/" + object;
        Server res = new Server(); java.rmi.Naming.bind(object, res);
        System.err.println("Listening server object " + object);
        
        return res;
    }

    // -------------------- IEntryEventListener -------------
    
    private Set<Integer> changesFromClients = new HashSet<Integer>();
    private boolean isUpdating = false;
    
    public void onNewFile(String activeFileName) {}
    
    public void onEntryActivated(SourceTextEntry newEntry) {
        if (isUpdating) return; // do not block GUI, better delay refresh
        synchronized(changesFromClients) { // also delay changes during refresh
            try {
                if (changesFromClients.size() > 0) {
                    Core.getEditor().commitAndDeactivate();
                    Core.getEditor().refreshEntries (changesFromClients);
                    Core.getEditor().activateEntry(); 
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                changesFromClients.clear(); // always clear, else changes may cummulate and slow down refresh!
            }
        }
    }

    // ------------------ IDistantExternalMemory -------------
    
    private Map<String,Iterator<SourceTextEntry>> openedCursors = new HashMap<>();
    private Map<String,SourceTextEntry> openedEntries = new HashMap<>();
    
    /** Opens a new cursor. Method to be sent by the client before others **/
    public String findChanges (long timeStamp) {
        IProject proj = Core.getProject();
        if (! (proj instanceof RealProject)) return null;
        
        final Collection<SourceTextEntry> usedEntries = new HashSet<>();
        for (SourceTextEntry ste: proj.getAllEntries()) {
             TMXEntry te = proj.getTranslationInfo(ste); if (! te.isTranslated()) continue;
             if ((te.changeDate > timeStamp) || (te.creationDate > timeStamp)) usedEntries.add(ste);
        }
        Iterator<SourceTextEntry> iter = usedEntries.iterator();
        openedCursors.put(iter.toString(), iter); return iter.toString();
    }
    
    // Next methods retreive entry content 
    // Parameter id must contain result from {findChanges}
    
    public boolean next(String id) throws RemoteException {
        Iterator<SourceTextEntry> iter = openedCursors.get(id); if (iter == null) throw new RemoteException("Cursor " + id + " does not exist");
        if (iter.hasNext()) { openedEntries.put(id, iter.next()); return true; } 
        else { openedEntries.remove(id); openedCursors.remove(id); return false; }
    }
    
    public String getEntrySource(String id) { return openedEntries.get(id).getSrcText(); }    
    public String getEntryTranslation(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return null; else return te.translation;
    }
    
    public String getEntryAuthor(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return null; else return te.creator;
    }
    
    public String getEntryLastModifier(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return null; else return te.changer;
    }
    
    public long getEntryCreationDate(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return 0; else return te.creationDate;
    }
    
    public long getEntryLastModificationDate(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return 0; else return te.changeDate;
    }
    
    public String getEntryNote(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return null; else return te.note;
    }
    
    public String[] getEntryKey(String id) { 
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id));
        if (te == null) return null; if (te.defaultTranslation) return null;
        EntryKey key = openedEntries.get(id).getKey();
        return new String[] { key.file, key.sourceText, key.id, key.prev, key.next, key.path };
    }    
    
    private ProjectMemory.ExtendedCheckOrphanedCallback entries;
     
    public void registerDefaultTranslation(String src, String tra) {
        if (entries == null) entries = new ProjectMemory.ExtendedCheckOrphanedCallback(Core.getProject().getAllEntries());
        SourceTextEntry ste = entries.existSource.get(src); if (ste == null) return; // do not store orphans
        PrepareTMXEntry pe = new PrepareTMXEntry(); pe.source = src; pe.translation = tra;
        Core.getProject().setTranslation(ste, pe, true, null); 
        synchronized(changesFromClients) { isUpdating = true; changesFromClients.add(ste.entryNum()); isUpdating = false; }
    }
    
    /** Register alternative translation. Parameters are split because they are not serializable **/
    public void registerAltTranslation(String src, String tra, String file, String id, String path, String prev, String next) {
        if (entries == null) entries = new ProjectMemory.ExtendedCheckOrphanedCallback(Core.getProject().getAllEntries());
        SourceTextEntry ste = entries.existKeys.get(new EntryKey(file, src, id, prev, next, path)); if (ste == null) return; // do not store orphans
        PrepareTMXEntry pe = new PrepareTMXEntry(); pe.source = src; pe.translation = tra;
        Core.getProject().setTranslation(ste, pe, false, null); 
        synchronized(changesFromClients) { isUpdating = true; changesFromClients.add(ste.entryNum()); isUpdating = false; }
    }
    
    public long timeStamp()  {
        return System.currentTimeMillis();
    }
}
