/**************************************************************************
 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 org.omegat.core.events.IProjectEventListener;

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

/** 
 * A server which opens a project, then stays in console mode, and receives changes via RMI. <br/>
 * Project is saved regularly as if you had an editor opened.
 * Usage : OmegaT --mode=console [--server-port=nnnn]? --server-object=any [project]
 **/
public class ProjectServer extends java.rmi.server.UnicastRemoteObject implements IDistantExternalMemory, IProjectEventListener {

    public static ProjectServer INSTANCE = null; // singleton class
    
    protected ProjectServer() throws RemoteException {
        CoreEvents.registerProjectChangeListener(this);
        INSTANCE = this; 
    }
    
    public static ProjectServer 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;
        ProjectServer res = new ProjectServer(); java.rmi.Naming.bind(object, res);
        System.err.println("Listening server object " + object);
        
        return res;
    }
    
    // -------------------- IProjectEventListener -------------
    
    public void onProjectChanged(IProjectEventListener.PROJECT_CHANGE_TYPE eventType) {
        switch (eventType) {
        case CLOSE: case CREATE: changesByDate.clear(); break;
        case LOAD:
            changesByDate.clear();
            // Pre-load already translated entries
            final Map<Long,SourceTextEntry> usedEntries = new TreeMap<>();
            IProject proj = Core.getProject(); for (SourceTextEntry ste: proj.getAllEntries()) {
                TMXEntry te = proj.getTranslationInfo(ste.getKey()); if (! te.isTranslated()) continue;
                long time = Math.max(te.creationDate, te.changeDate);
                while (usedEntries.containsKey(time)) time++;                
                usedEntries.put(time, ste);
            }
            // now convert to Deque
            for (Map.Entry<Long,SourceTextEntry> me: usedEntries.entrySet()) changesByDate.addLast(me.getValue());
        }
    }
    
    // ------------------ IDistantExternalMemory -------------
    
    protected Deque<SourceTextEntry> changesByDate = new java.util.concurrent.ConcurrentLinkedDeque<>();
    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;
                
        Iterator<SourceTextEntry> iter = changesByDate.descendingIterator(); // most recent first!!!
        openedCursors.put(iter.toString() + "#" + timeStamp, iter); return iter.toString() + "#" + timeStamp;
    }
    
    // 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()) 
            try { 
                SourceTextEntry next = iter.next(); TMXEntry te = Core.getProject().getTranslationInfo(next.getKey());
                long timeStamp = Long.parseLong(id.substring(id.lastIndexOf('#') + 1));
                if ((te.changeDate > timeStamp) || (te.creationDate > timeStamp)) {
                    openedEntries.put(id, iter.next()); return true;
                }
                // else: end the loop here, so, clean and return false
            } catch (Exception e) {
                org.omegat.util.Log.log(e);
            }
        // If not true, either because iter had not next or because this element was too old
        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).getKey());
        if (te == null) return null; else return te.translation;
    }
    
    public String getEntryAuthor(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        if (te == null) return null; else return te.creator;
    }
    
    public String getEntryLastModifier(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        if (te == null) return null; else return te.changer;
    }
    
    public long getEntryCreationDate(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        if (te == null) return 0; else return te.creationDate;
    }
    
    public long getEntryLastModificationDate(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        if (te == null) return 0; else return te.changeDate;
    }
    
    public String getEntryNote(String id) {
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        if (te == null) return null; else return te.note;
    }
    
    public String[] getEntryKey(String id) { 
        TMXEntry te = Core.getProject().getTranslationInfo(openedEntries.get(id).getKey());
        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 };
    }    
    
    protected 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); setSourceEntryModified(ste);
    }
    
    /** 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); setSourceEntryModified(ste);
    }
    
    public void setSourceEntryModified(SourceTextEntry ste) {
        changesByDate.remove(ste); changesByDate.addLast(ste); // must be here only once, and at last position
    }
    
    public long timeStamp()  {
        return System.currentTimeMillis();
    }
}
