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

 Copyright (C) 2016-2021 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.filehooks;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.FileAlreadyExistsException;
import static java.nio.file.StandardCopyOption.*;

import org.omegat.core.Core;
import org.omegat.util.Log;
import org.omegat.util.Preferences;
 

/**
 * Hook used by SDLXLIFF files: can call Trados command line
 * 
 * @author Thomas CORDONNIER
 */
public class SdlxliffFileHook extends XliffFileHook {
        
    /** Cross-compilation: creates an OmegaT project with source = native file and compile it with current memory **/
    @Override 
    public void postCompile(File destDir, String midName) {
        lastCompileException = null;
        try {
            if (Preferences.getPreferenceDefault(Preferences.TRADOS_COMMAND_LINE,"").length() > 0)
                try { Log.log("Using command-line compiler"); callTradosCommandLine(destDir.getCanonicalPath() + File.separator + midName); }
                catch (Exception e) { 
                    Log.log("Error in calling Studio: " + e); Log.log(e);
                    if (Preferences.isPreferenceDefault(Preferences.TRADOS_USE_XCOMPILE, false)) super.postCompile (destDir, midName);
                    else lastCompileException = e; // to be kept by the UI
                }
            else
                if (Preferences.isPreferenceDefault(Preferences.TRADOS_USE_XCOMPILE, false)) super.postCompile (destDir, midName);
                else lastCompileException = new Exception("Trados command line path not configured and cross-compilation inactive: cannot generate target files");
        } catch (Exception e) {
            lastCompileException = e; e.printStackTrace(); Log.log(e);
        }
    }
    
    /**
     * Call Trados command line to generate target native
     * @param tradosTargetXliff		Location of the SDLXLIFF in the Trados project (which must already exist!)
     * @param omegatTargetXliff		Location of the SDLXLIFF in OmegaT project's target directory (compilation already called, so the file should exist)
     **/
    private void callTradosCommandLine(String omegatTargetXliff) throws Exception {
        String tradosTargetXliff = readOriginalFileName(omegatTargetXliff).getCanonicalPath();
        // 1. check that target trados xliff exists
        boolean dummy = false;
		File f = new File(tradosTargetXliff); if (! f.exists()) { 
			// Hypothesis 1: file location was declared in <file original> 
			f = new File(tradosTargetXliff.toString() + ".sdlxliff"); 
			if (! f.exists()) { 
				// Hypothesis 2: file is in Trados standard path
				Log.log("SDLXLIFF is not in " + f + ", searching in Trados configured path");
				f = findTradosProject(omegatTargetXliff);
				if (f == null) { f = new File(createLocalTradosProject (new File(omegatTargetXliff))); dummy = true; }
            }
		}
        // 2. check that target trados xliff is in a real Trados project
        StringBuffer parents = new StringBuffer();
        PARENTLOOP: while (true) {
            f = f.getParentFile(); parents.insert(0, f.getName() + File.separator);
            if (f == null) {
                lastCompileException = new Exception(new File(tradosTargetXliff).getName() + " is not in an Trados project.\n A correct Trados project in your computer is needed to convert sdlxliff to docx");
                return; // stop here.
            }
			for (File child: f.listFiles()) if (child.getName().endsWith(".sdlproj")) break PARENTLOOP;
        }
        parents.delete (0, parents.indexOf(File.separator) + File.separator.length()); parents.delete (0, parents.indexOf(File.separator)); 
        tradosTargetXliff = f.getCanonicalPath() + File.separator + Core.getProject().getProjectProperties().getTargetLanguage() 
            + parents.toString() + tradosTargetXliff.substring(tradosTargetXliff.lastIndexOf(File.separator) + File.separator.length());
        // 3. erase SDLXLIFF in Trados project, because OmegaT contains the copy with the translations
        Files.copy(Paths.get(omegatTargetXliff), Paths.get(tradosTargetXliff + ".sdlxliff"), REPLACE_EXISTING); 
        // 4. Call the command line and read STDOUT/ERR
        Core.getMainWindow().showProgressMessage("Calling Trados Studio to build binary file");
        String[] execParams = Preferences.getPreference(Preferences.TRADOS_COMMAND_LINE).split(" "); 
        if (! (execParams[0].contains("\\") || execParams[0].contains("/"))) execParams[0] = Preferences.getPreference(Preferences.TRADOS_PATH) + "\\" + execParams[0];
        for (int i = 1; i < execParams.length; i++) {
			execParams[i] = execParams[i].replace ("%s", f.getCanonicalPath() + File.separator + f.getName() + ".sdlproj")	// compatibility
				.replace ("%projPath", f.getCanonicalPath() + File.separator + f.getName() + ".sdlproj");	// new version, better
			if (execParams[i].contains("%file")) {
				File otFile = new File(omegatTargetXliff);
				execParams[i] = execParams[i].replace ("%fileName", otFile.getName()).replace("%fileOmegatPath", otFile.getCanonicalPath().substring(Core.getProject().getProjectProperties().getTargetRoot().length()));
			}
		}
        Log.log("Running '" + execParams[0] + "' '" + execParams[1] + "'");
        if (! (new File (execParams[0]).exists())) {
            lastCompileException = new Exception("Trados converter not found.\n Please check your configuration and try again."); 
            Log.log ("Trados caller program not found : " + execParams[0]);
            Log.log("Exe not found: '" + execParams[0] + "'");
            return; // cannot continue
        }
        Core.getMainWindow().showProgressMessage("Running Trados");
        Log.log("Starting process");
        Process proc = Runtime.getRuntime().exec (execParams); 
        String line; String tradosError = null;
        Log.log("Reading getInputStream");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader (proc.getInputStream()))) {
            while ((line = reader.readLine()) != null) { Log.log ("[TRADOS OUT]: " + line); if (line.startsWith("[Error]")) tradosError = line; }
        }
        Log.log("Reading getErrorStream");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader (proc.getErrorStream()))) {
            while ((line = reader.readLine()) != null) { Log.log ("[TRADOS OUT]: " + line); if (line.startsWith("[Error]")) tradosError = line; }
        }
        proc.waitFor(); 
        // 5. Check for existence of the target native file
        Log.log("Searching for " + tradosTargetXliff);
        if (new File(tradosTargetXliff).lastModified() > new File(omegatTargetXliff).lastModified()) {
            File nativeDest = new File(Core.getProject().getProjectProperties().getProjectRoot(), COMPILED_NATIVE_DIR); nativeDest.mkdirs();
            nativeDest = new File(nativeDest, tradosTargetXliff.substring(tradosTargetXliff.lastIndexOf(File.separator) + File.separator.length()));
            if (Preferences.isPreferenceDefault(Preferences.TRADOS_RENAME_METHOD, false)) {
                String name = nativeDest.getName();
                if (dummy) name = name.substring(0, name.lastIndexOf('.')) + "-dummyProject" + name.substring(name.lastIndexOf('.'));
                else name = name.substring(0, name.lastIndexOf('.')) + "-existingProject" + name.substring(name.lastIndexOf('.'));
                nativeDest = new File(nativeDest.getParentFile(), name);
            }
            Files.copy(Paths.get(tradosTargetXliff), nativeDest.toPath(), REPLACE_EXISTING);
            nativeTarget = nativeDest.getCanonicalPath();
        }
        else if (tradosError != null) lastCompileException = new Exception( "Could not open file generated by Trados.\nTrados says:\n" + tradosError);
        else lastCompileException = new Exception( "Could not open file generated by Trados.\nPlease see log for details.");
    }
    
    /** 
     * Try to find the file in Trados standard path
     * @param otSdlxliff        File in OmegaT target
     * @return file in project if found, null otherwise
     **/
    public static File findTradosProject(String otSdlxliff) throws IOException {
        String tradosPath = Preferences.getPreference(Preferences.TRADOS_PROJ_PATH);
        if ((tradosPath == null) || (tradosPath.length() == 0)) {
            tradosPath = Preferences.getPreference(Preferences.TRADOS_PATH);
            if ((tradosPath != null) && (tradosPath.length() > 0)) {
                if (tradosPath.contains("\\")) tradosPath = tradosPath.substring(tradosPath.lastIndexOf("\\") + 1);
                
                if (tradosPath.endsWith("Studio2")) tradosPath = "Studio 2011";
                else if (tradosPath.endsWith("Studio3")) tradosPath = "Studio 2014";
                else if (tradosPath.endsWith("Studio4")) tradosPath = "Studio 2015";
                else if (tradosPath.endsWith("Studio5")) tradosPath = "Studio 2017";
                else if (tradosPath.endsWith("Studio6")) tradosPath = "Studio 2019";
                else if (tradosPath.endsWith("Studio7") || tradosPath.endsWith("Studio16")) tradosPath = "Studio 2021";
                
                tradosPath = new File(javax.swing.filechooser.FileSystemView.getFileSystemView().getDefaultDirectory(), tradosPath + "\\Projects").toString();
            }
        }
    
        if ((tradosPath != null) && (tradosPath.length() > 0)) {
            Log.log("Exploring " + tradosPath);
            FileInfo info = readOriginalFileInfo (new File(otSdlxliff));
            if (info.traLang == null) info.traLang = Core.getProject().getProjectProperties().getTargetLanguage().getLanguage();
            String sxFileName = info.original.getCanonicalPath(); sxFileName = sxFileName.substring(sxFileName.lastIndexOf("\\") + 1) + ".sdlxliff";
            
            String tradosProjModel = Preferences.getPreference(Preferences.TRADOS_PROJ_MODEL);
            java.util.function.Predicate<File> validProject = null;
            if ((tradosProjModel == null) || (tradosProjModel.length() == 0)) validProject = File::isDirectory;
            else {
                String fileModel = Preferences.getPreference(Preferences.TRADOS_FILE_MODEL);
                if ((fileModel != null) && (fileModel.length() > 0)) {
                    fileModel = fileModel.replace("?","(.)").replace("*","(.+?)");
                    java.util.regex.Matcher MATCH_FILE = java.util.regex.Pattern.compile(fileModel).matcher(sxFileName);
                    if (MATCH_FILE.find()) { // tradosProjModel =~ s/\$(\d+)/MATCH_FILE[$1]/e
                        java.util.regex.Matcher replaceMatcher = org.omegat.core.search.TextExpression.RegexReplaceExpression.REGEX_REPLACE_VARIABLE.matcher(tradosProjModel);
                        while (replaceMatcher.find()) {
                            int varId = Integer.parseInt(replaceMatcher.group(2));
                            tradosProjModel = tradosProjModel.substring(0, replaceMatcher.start())
                                + replaceMatcher.group(1)
                                + MATCH_FILE.group (varId) // yes, from source matcher!
                                    .replace("\\","\\\\").replace("$","\\$")	// avoid re-eval inside replaceCase
                                + tradosProjModel.substring(replaceMatcher.end());
                            replaceMatcher.reset(tradosProjModel);
                        }
                    }
                }
                final java.util.regex.Pattern PTN_PROJ = java.util.regex.Pattern.compile(tradosProjModel.replace("?","(.)").replace("*","(.+?)"));
                Log.log("Trados project model for " + fileModel + " = " + tradosProjModel);
                validProject = (child) -> {
                    if (! child.isDirectory()) return false;
                    return PTN_PROJ.matcher(child.getName()).find();
                };
            }
            for (String tp: tradosPath.split(";")) 
                for (File child: new File(tp).listFiles()) if (validProject.test(child)) {
                    Log.log("Exploring " + child + " for " + info.traLang + "\\" + sxFileName);
                    File f = new File(child, "" + info.traLang + "\\" + sxFileName);
                    if (f.exists()) { Log.log("In-project file found at " + f); return f; }
                }
        }
        return null;
    }
    
    private String createLocalTradosProject (File omegatTargetXliff) throws IOException {
        FileInfo info = readOriginalFileInfo (omegatTargetXliff);
        if (info.srcLang == null) info.srcLang = Core.getProject().getProjectProperties().getSourceLanguage().getLanguage();
        if (info.traLang == null) info.traLang = Core.getProject().getProjectProperties().getTargetLanguage().getLanguage();
		try {
			return createCommandTradosProject(omegatTargetXliff, info);
		} catch (Exception e) {
			return createDummyTradosProject(omegatTargetXliff, info);
		}
	}
	
    private String createCommandTradosProject (File omegatTargetXliff, FileInfo info) throws Exception {
        String[] execParams = Preferences.getPreference(Preferences.TRADOS_COMMAND_LINE).split(" "); 
        if (! (execParams[0].contains("\\") || execParams[0].contains("/"))) execParams[0] = Preferences.getPreference(Preferences.TRADOS_PATH) + "\\" + execParams[0];
        if (! (new File (execParams[0]).exists())) throw new FileNotFoundException(execParams[0]);
		execParams = new String[] { execParams[0], "--create-full", 
			Core.getProject().getProjectProperties().getProjectRoot() + File.separator + "Studio",
			info.srcLang, info.traLang,
			omegatTargetXliff.toString()
		};
        Core.getMainWindow().showProgressMessage("Running Trados");
        Log.log("Starting process");
        Process proc = Runtime.getRuntime().exec (execParams); 
        String line; String tradosError = null;
        Log.log("Reading getInputStream");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader (proc.getInputStream()))) {
            while ((line = reader.readLine()) != null) { Log.log ("[TRADOS OUT]: " + line); if (line.startsWith("[Error]")) tradosError = line; }
        }
        Log.log("Reading getErrorStream");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader (proc.getErrorStream()))) {
            while ((line = reader.readLine()) != null) { Log.log ("[TRADOS OUT]: " + line); if (line.startsWith("[Error]")) tradosError = line; }
        }
        proc.waitFor();
		
		File res = new File(Core.getProject().getProjectProperties().getProjectRoot() + File.separator + "Studio");
		if (! res.exists()) throw new Exception ("Could not create project");
		res = new File(res, info.traLang + File.separator + omegatTargetXliff);
		if (! res.exists()) throw new Exception ("Could not create project");
		return res.toString();
	}
		
    private String createDummyTradosProject (File omegatTargetXliff, FileInfo info) throws IOException {
        String root = Core.getProject().getProjectProperties().getProjectRoot();
        try { Files.createDirectory (Paths.get(root + "\\Studio")); } catch (FileAlreadyExistsException fe) { /* only this one to skip */ }
        String fileStr = omegatTargetXliff.getName(); fileStr = fileStr.substring(0, fileStr.length() - 9);
        try (PrintWriter out = new PrintWriter(new File(root + "\\Studio\\Studio.sdlproj"), "UTF-8")) {
            out.println("<?xml version=\"1.0\"?>");
            out.println("<Project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" Version=\"4.0.0.0\">");
            out.println("  <LanguageDirections>");
            out.println("    <LanguageDirection TargetLanguageCode=\"" + info.traLang + "\" SourceLanguageCode=\"" + info.srcLang + "\" />");
            out.println("  </LanguageDirections>");
            out.println("  <GeneralProjectInfo />");
            out.println("  <SourceLanguageCode>" + info.srcLang + "</SourceLanguageCode>");
            out.println("  <ProjectFiles>");
            out.println("    <ProjectFile Path=\"\" Role=\"Translatable\" Name=\"" + fileStr + "\">");
            out.println("      <LanguageFiles>");
            out.println("         <LanguageFile LanguageCode=\"" + info.traLang + "\">");
            out.println("             <FileVersions>");
            out.println("                 <FileVersion PhysicalPath=\"" + info.traLang + "\\" + omegatTargetXliff.getName() + "\" />");
            out.println("             </FileVersions>");
            out.println("         </LanguageFile>");
            out.println("      </LanguageFiles>");
            out.println("    </ProjectFile>");
            out.println("  </ProjectFiles>");
            out.println("</Project>");
        }
        try { Files.createDirectory (Paths.get(root + "\\Studio\\" + info.traLang));  } catch (FileAlreadyExistsException fe) { /* only this one to skip */ }
        File destFile = new File(root + "\\Studio\\" + info.traLang + "\\" + fileStr + ".sdlxliff");
        if (info.original.exists()) { // enough to copy the file as is
            Log.log ("convertDummyProject: original file exists as declared in <original>");
            Files.copy(omegatTargetXliff.toPath(), destFile.toPath(), REPLACE_EXISTING);
        } else if (new File(root + "\\original\\" + fileStr).exists()) {	// copy, but change the path inside the file
            Log.log ("convertDummyProject: original file exists in project/original directory");
            CopyXliffChangePath(omegatTargetXliff, destFile, root + "\\original\\" + fileStr, null);
        } else if (new File(root + "\\" + SOURCE_NATIVE_DIR + "\\" + fileStr).exists()) {	// copy, but change the path inside the file
            Log.log ("convertDummyProject: original file exists in project/original directory");
            CopyXliffChangePath(omegatTargetXliff, destFile, root + "\\" + SOURCE_NATIVE_DIR + "\\" + fileStr, null);
        } else {	// we must extract the file from the BASE64, but also replace file path in <file original>       
            Log.log ("convertDummyProject: extract original file from <internal>");
            Core.getMainWindow().showProgressMessage("Extracting Base64 from " + omegatTargetXliff);
            StringBuffer buf = new StringBuffer(); String sourceNativeFileName = root + "\\Studio\\" + info.srcLang + "\\" + fileStr;
            CopyXliffChangePath(omegatTargetXliff, destFile, sourceNativeFileName, buf);
            try { Files.createDirectory (Paths.get(root + "\\Studio\\" + info.srcLang)); } catch (FileAlreadyExistsException fe) { /* only this one to skip */ }			
            base64ToFile(buf, sourceNativeFileName);
        }
        String destFileName = destFile.getCanonicalPath(); destFileName = destFileName.substring(0, destFileName.lastIndexOf(".")); return destFileName;
    }
    
    /** Copy the file to dest, but change original="xxx" inside the contents, and eventually extract Base64 if buffer is not null **/
    private static void CopyXliffChangePath(File sdlxliff, File dest, String originalPath, StringBuffer base64Buffer) throws IOException {
        Log.log ("CopyXliffChangePath (" + sdlxliff.getCanonicalPath() + " -> " + dest + ") (original => " + originalPath + ")");
        try (
            InputStream is = new FileInputStream (sdlxliff); BufferedReader reader = new BufferedReader(new InputStreamReader (is, "UTF-8"));
            PrintWriter xliffOut = new PrintWriter (dest, "UTF-8");
        ) {
            String line = ""; do { 
                line = reader.readLine(); 
                xliffOut.println(
                    ORI_FIND_PATTERN.matcher(line).replaceAll("original = \"" + originalPath.replace("\\", "\\\\") + "\""));
            } while (! line.contains("<internal-file"));
            if (base64Buffer != null) {
                line = line.substring(line.indexOf("<internal-file")); line = line.substring(line.indexOf('>') + 1); base64Buffer.append(line);
                do { line = reader.readLine(); base64Buffer.append(line); xliffOut.println(line); } while (! line.contains("</internal-file>"));
                base64Buffer.setLength(base64Buffer.indexOf("<"));
            }
            while ( (line = reader.readLine()) != null ) xliffOut.println(line);
        }
    }

}
