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

 Copyright (C) 2016-2022 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 {
		    File oriFileXliff = readOriginalFileName(destDir.getCanonicalPath() + File.separator + midName);
			
            if (Preferences.getPreferenceDefault(Preferences.TRADOS_COMMAND_LINE,"").length() > 0)
                try { Log.log("Using command-line compiler"); callTradosCommandLine(oriFileXliff.getCanonicalPath(), 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 tradosTargetXliff, String omegatTargetXliff) throws Exception {
        // 1. check that target trados xliff exists
		boolean dummy = false;
	File f = new File(tradosTargetXliff); if (! f.exists()) { 
		f = new File(tradosTargetXliff.toString() + ".sdlxliff"); 
		if (! f.exists()) { f = new File(createDummyTradosProject (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
        try { Files.copy(Paths.get(omegatTargetXliff), Paths.get(tradosTargetXliff + ".sdlxliff"), REPLACE_EXISTING); }
        catch (Exception ex) { // maybe target language differs between OmegaT and Trados project
            FileInfo info = readOriginalFileInfo(new File(omegatTargetXliff));
            tradosTargetXliff = tradosTargetXliff.replace("" + File.separator + Core.getProject().getProjectProperties().getTargetLanguage() + File.separator,
                File.separator + info.traLang + File.separator);
            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.");
	}
	
	private String createDummyTradosProject (File omegatTargetXliff) throws IOException {
        String root = Core.getProject().getProjectProperties().getProjectRoot();
		try { Files.createDirectory (Paths.get(root + "\\Studio")); } catch (FileAlreadyExistsException fe) { /* only this one to skip */ }
        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();
        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);
        }
    }

}
