package org.vishia.checkDeps_C;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.vishia.mainCmd.MainCmdLogging_ifc;
import org.vishia.util.Debugutil;
import org.vishia.util.FileSystem;



/**This class contains the methods to check one source file with its dependencies.
 * The main routine is {@link #processIncludeLine(String, String, AddDependency_InfoFileDependencies, ObjectFileDeps, TreeMap, String, int)},
 * which returns the boolean value need or doesn't need a translation.
 * <br><br>
 * The newness of any source file in comparison with the produced translated file (result file) can be ascertain with several conditions:
 * <ul>
 * <li>Timestamp check: The timestamp of the result file is compared with the timestamp of all source files. 
 *     This is the classic method, used by makers often.
 *     <ul> <li> Advantage: Fast detection of newness.
 *     <li> Disadvantage:
 *       <ul><li>If a new file is produced from another tool but without any effective changes, it is detect as newly.
 *          Therefore it is regarded in the translation process. If all source files are produced newly in that way, 
 *          this method is ineffective.
 *          <li>If a older file should be taken with other content, it is not recognized as changed (as newly for translation).
 *       </ul>
 *     </ul>      
 * <li>Comparison with stored last file: This may be seems as a expensive method. 
 *     It allows to compare whether only productive code is changed, in opposite to comments, 
 *     which are not effective in the translated result. This method should be used if the sources are generated
 *     from another tool, which produces new files without real changes but with ineffective-for-translation comments. 
 *     <ul> <li> Advantage: Correct detection of newly sources.
 *     <li> Disadvantage: There should be a build-source-bundle at a second position in the file system.
 *     </ul>  
 *     The comparison of source files with comment-detection need only a few milliseconds per file. 
 *     It may be some times faster than a translation process. 
 * <li>Comparison of timestamp and size of the source file with the last used: The timestamp and file size should be noted
 *   in a textual list. This text list is used to compare the current values.
 *   <ul> <li> Advantage: 
 *     <ul><li>Fast detection of newness.
 *     <li>Correct detection if older files are used. This method doesn't works correct 
 *         only if the size and timestamp is equal but the content is different.
 *         That situation can occur only if the files are patched.
 *     </ul>
 *   <li>Disadvantages: In opposite to comparison of files no detection of only comments. The method isn't usefull
 *       if the source files are generated by a tool with different informations in comment, which are ineffective for the translation.
 *   </ul>          
 *   <li> Disadvantage: There should be a build-source-bundle at a second position in the file system.
 *   </ul>
 * </ul>     
 * <br><br>
 * <bold>Dependency detection:</bold>
 * Dependencies are included or used files while translation. This algorithm is used for C/C++-Sources.
 * The #include statement is responsible to include.
 * <ul>
 * <li>If the source is recognized as 'not newly' against the result (C-object file), the dependencies are checked
 *   in a file parallel to the object file with localname.dep. It contains the absolute or an relative path
 *   to the included files. That files are checked.
 * <li>If the sources is recognized as 'not newly', but the dependency file doesn't exists, 
 *   the source should be analyzed for dependencies. That is done in the file comparison.
 * <li>If the newly of the source should be detected by comparison of content, the primary dependencies are detected
 *   while that comparison process runs. A dependency-file ins't necessary. But it will be produced for manually visitation.
 * </ul>      
 * <br><br>
 * <bold>Include Path and included files:</bold>
 * The include path is given in the config file. It is valid for all sources. It is the common include path. 
 * An additional include path may be given for specified -src=SRCPATH files with an additional calling argument -srcInclpath=PATH.
 * <br><br>
 * The included files are named usual only with its name or with a prefix path, like
 * <pre>
 *   #include "file.h"
 *   #include "path/file.h"
 * </pre>
 * For all included files, which are found in the common include path, an index is filled. In that kind an included file,
 * which was found and processed one time, can be re-found with a short index access and can be detected as 'already processed'.
 * An instance {@link InfoFileDependencies} are related to it.
 * Included files, which are not found in the common include path, should be searched in the additional path. 
 * Then the absolute path is known and that included files can be found with its absolute path in the same list.
 *     
 * @author Hartmut Schorrig
 *
 */
public class CheckDependencyFile
{

  /**Version, history and license.
   * <ul>
   * <li>2017-08-30 Hartmut new: {@link #setDirObj(String)} throws an Exception this behavior is set by {@link #bExc}.
   * <li>2017-08-30 Hartmut new: {@link #processSrcfile(File, String)} only with this 2 arguments should be used in zmake, see new documentation.
   * <li>2014-05-10 Hartmut new: {@link #processSrcfile(File, String, File, String)} with given Object file.
   *   Therefore the routine {@link #setDirObj(String)} is not necessary to call before. Any file can have its object directory.
   * <li>2013-10-13 Hartmut chg: prevent circular including with a Map of already processed files, idxOnce as Thread data (Parameter).
   *   TODO: It does not enter the cyclic included files in the dependency of the deeper file. 
   *   a->b->c->a  a depends on b and c, but b depends on a too, c depends on a,b. If a will be processed first, the dependency of b, c to a are not entered.
   * <li>2013-03-03 Hartmut chg: catch in {@link #processSrcfile(File, ObjectFileDeps, int)}. It's a fix change.
   * <li>2013-01-06 Hartmut chg: Now supports more as one obj file. This is necessary if one make process compiles several versions.
   *   The routine {@link #setDirObj(String)} is changed in parameter form.
   * <li>2013-01-06 Hartmut chg order of search in {@link #searchInclFileInIncludePath(String, List)}, not at last 'y'.
   *   Elsewhere not founded includefiles are not reported.
   * <li>2012-12-25 Hartmut chg: Now This class is able to use independently from {@link CheckDeps}. It should be used
   *   to check file by file. All other classes are adapted. 
   * <li>2012-12-25 Hartmut new: Inserted in the Zbnf component because it is an integral part of the Zmake concept
   *   for C-compilation.
   * <li>2011-05-00 Hartmut created: It was necessary for C-compilation to check real dependencies in a fast way.
   *   The dependency check from a GNU compiler was to slow.
   * </ul>
   * <br><br>
   * <b>Copyright/Copyleft</b>:
   * For this source the LGPL Lesser General Public License,
   * published by the Free Software Foundation is valid.
   * It means:
   * <ol>
   * <li> You can use this source without any restriction for any desired purpose.
   * <li> You can redistribute copies of this source to everybody.
   * <li> Every user of this source, also the user of redistribute copies
   *    with or without payment, must accept this license for further using.
   * <li> But the LPGL ist not appropriate for a whole software product,
   *    if this source is only a part of them. It means, the user
   *    must publish this part of source,
   *    but don't need to publish the whole source of the own product.
   * <li> You can study and modify (improve) this source
   *    for own using or for redistribution, but you have to license the
   *    modified sources likewise under this LGPL Lesser General Public License.
   *    You mustn't delete this Copyright/Copyleft inscription in this source file.
   * </ol>
   * If you are intent to use this sources without publishing its usage, you can get
   * a second license subscribing a special contract with the author. 
   * 
   * @author Hartmut Schorrig = hartmut.schorrig@vishia.de
   * 
   */
  public static final String version = "2017-08-30";

  /**Contains all dependencies, read from file and processed. */
  final CheckData checkData;
  
  /**If true then an IllegalArgumentException will be thrown instead text return. 
   * That is the standard java behavior. */
  final boolean bExc;
  
  /**Junction between the dependency data in {@link #checkData} and the dependency input and output textual file. */
  final CheckAllDepFile readerInputDepFile;

  /**Some paths etc. */
  final CfgData cfgData;

  final MainCmdLogging_ifc console;
  
  /**The dirSrcMirror refers to the build root directory. It is null, if the calling argument -srcBuild= isn't given.
   * All input paths are located inside this directory tree. 
   * For the src directories given with -src= calling argument, a local path part can be given.
   * The argument is written in form PATH:PATHLOCAL. The PATHLOCAL is used inside that attribute.
   * Additional the local path of the files are regarded. 
   */
  File dirSrcMirrorRoot;

  //final File dirDepsRoot;
  
  /**The dirObj refers to the object root directory.
   */
  File XXXdirObjRoot;

  
  /**The dependency-file which war read. Used for write.
   * 
   */
  String sFileDeps;
  
  List<String> dirObjRoots = new LinkedList<String>();

  
  String sDbgLocalFile;
  
  
  /**Constructor. The instance can be used for all files, which are located in the same source pool
   * and in the same object pool.
   * @param log for logging output
   * @param bExc If true then errors throws an exception. false then errors results in an return string which should be test by the application.
   *   See description of the methods.
   */
  public CheckDependencyFile(MainCmdLogging_ifc log, boolean bExc)
  {
    CheckData checkData = new CheckData();
    this.bExc = bExc;
    this.cfgData = new CfgData();
    this.checkData = checkData;
    this.readerInputDepFile = new CheckAllDepFile(cfgData, log, checkData);
    this.console = log;
  }
  
  
  
  public String setDirSrcMirror(String sDirSrcMirror){
    String sError = null;
    try{ 
      dirSrcMirrorRoot = FileSystem.mkDirPath(sDirSrcMirror + "/");   //assert that the directory exists. A build file can written into.
    }
    catch(IOException exc){ sError = "CheckDeps - Problem with source mirror path; " + exc.getMessage(); }
    return sError;
  }
  
  
  
  /**Set an object directory. This routine can be invoked more as one time to support more as one 
   * Object directories.
   * @param sDirObjExt Should have the form "path/*.objExt"
   * @return
   */
  public String setDirObj(String sDirObjExt){
    String sError = null;
    if(sDirObjExt.indexOf("/*.")<0){
      sError = "CheckDeps - Format of obj path, should be \"path/*.objext\"; " + sDirObjExt;
    }
    else {
      try{ 
        //dirObjRoot = 
        FileSystem.mkDirPath(sDirObjExt);   //assert that the directory exists. A build file can written into.
        dirObjRoots.add(sDirObjExt);
      }
      catch(IOException exc){ sError = "CheckDeps - Problem with obj path; " + exc.getMessage(); }
    }
    if(sError !=null && bExc) throw new IllegalArgumentException("CheckDependencyFile.setDirObj - error: " + sError);
    return sError;
  }
  
  
  
  public void setDebugLocalFileContains(String part) {
    sDbgLocalFile = part;
  }
  
  
  
  /**This routine must be called after construction to supply the configuration data.
   * It is not a part of constructor because it may be evaluated separately.
   * Any error in config file provides a string return or an exception depending on the ctor parameter exc  
   * @param sFileCfgData The file
   * @param currdir the directory which is used as base to build the path if the config file contains relative paths.
   * @return null if no error. An error message. If not null, this class is not able to use.
   */
  public String readCfgData(String sFileCfgData, File currdir)
  {
    ParserConfigFile parserCfg = new ParserConfigFile(cfgData, console, currdir);
    String sError = parserCfg.parseConfigFile(sFileCfgData);
    if(sError !=null && bExc) throw new IllegalArgumentException("CheckDependencyFile.readCfgData - read error; " + sError);
    return sError;
  }
  
  
  
  /**This routine can be called after construction to supply the configuration data.
   * It is not a part of constructor because it may be evaluated separately.
   * Any error in config file provides a string return.  
   * <br><br>
   * If this routine is not called, there are not known dependencies. So any file is checked completely.
   * 
   * @param sFileDepenencies The last created dependency file.
   * @return null if no error. An error message. If not null, this class is not able to use.
   */
  public String readDependencies(String sFileDepenencies){
    this.sFileDeps = sFileDepenencies;
    readerInputDepFile.readDepFile(sFileDepenencies);

    return null;
  }
  
  
  
  
  
  
  
  
  /**Builds the mirror of the primary source file for compare and build.
   * @param fileSrc The primary source file
   * @param sLocalPath The local "path.name.ext" of the file.
   *        It is used to build the second source file with the appropriate name 
   *        in the appropriate sub-directory.
   * @return null if the calling argument -srcBuild= isn't given. Then no mirror files are used.
   *         null if the sLocalPath is null. This method is called also if a file isn't in source pool.
   *         The File instance of the mirror-source file in a existing directory. 
   *         The mirror-source file can be exists or not, but the directory will be created always.
   */
  File getFileSrcMirror(String sLocalPath){
    final File fileSrcMirror;
    if(dirSrcMirrorRoot !=null && sLocalPath !=null){
      fileSrcMirror = new File(this.dirSrcMirrorRoot, sLocalPath);
      try{ FileSystem.mkDirPath(fileSrcMirror); }
      catch(IOException exc){ throw new RuntimeException(exc); }
    } else {
      fileSrcMirror = null;
    }
    return fileSrcMirror;  //may be null
  }



  
  /**Check of one source file for newly against the associated result file of translation (object-file for C-compilation). 
   * @param fileSrc The source file
   * @param sLocalPathName The local part of name, it is the name of the object file
   * @param sObjExt extension of the object file
   * @return An ample information about dependencies. This information is used to produce
   *  the {@link #writeDependencies(String)} line for this file.
   */
  public InfoFileDependencies processSrcfile(File fileSrc, String sLocalPathName, File dirObjRoot, String sObjExt) 
  {
    final ObjectFileDeps objDeps;
    
    if(sDbgLocalFile !=null && sLocalPathName.contains(sDbgLocalFile)) {
      stop();
    }
    int posExt = sLocalPathName.lastIndexOf('.');
    String sExt = sLocalPathName.substring(posExt+1);
    //
    if(sExt.startsWith("c") || sExt.startsWith("C") || sExt.equals("s") || sExt.equals("S")){
      objDeps = new ObjectFileDeps(dirObjRoot, sLocalPathName, sObjExt); 
      objDeps.createObjDir(console);
    } else {
      objDeps = null;
    }
    InfoFileDependencies infoFile = processSrcfile(fileSrc, objDeps, 0, new TreeMap<String, String>());
    if(objDeps !=null && objDeps.isObjDeleted()){ 
      checkData.nrofDelObj +=1; 
    }
    if(objDeps !=null && objDeps.isObjRecompile()){ 
      checkData.nrofRecompilings +=1; 
    }
    return infoFile;
  }


  

  
  /**Check of one source file for newly against the associated result file of translation (object-file for C-compilation). 
   * @param fileSrc The source file
   * @param sLocalPathName The local part of name, it is the name of the object file
   * @param sObjExt extension of the object file
   * @return An ample information about dependencies. This information is used to produce
   *  the {@link #writeDependencies(String)} line for this file.
   */
  public InfoFileDependencies processSrcfile(File fileSrc, String sLocalPathName) 
  {
    final ObjectFileDeps objDeps;
    
    if(sDbgLocalFile !=null && sLocalPathName.contains(sDbgLocalFile)) {
      Debugutil.stop();
    }
    int posExt = sLocalPathName.lastIndexOf('.');
    String sExt = sLocalPathName.substring(posExt+1);
    //
    if(sExt.startsWith("c") || sExt.startsWith("C") || sExt.equals("s") || sExt.equals("S")){
      objDeps = new ObjectFileDeps(dirObjRoots, sLocalPathName); 
      objDeps.createObjDir(console);
    } else {
      objDeps = null;
    }
    InfoFileDependencies infoFile = processSrcfile(fileSrc, objDeps, 0, new TreeMap<String, String>());
    if(objDeps.isObjDeleted()){ 
      checkData.nrofDelObj +=1; 
    }
    if(objDeps.isObjRecompile()){ 
      checkData.nrofRecompilings +=1; 
    }
    return infoFile;
  }

  
  
  /**See {@link #processSrcfile(File, String)}
   * @param sObjExt This attribute is unnecessary
   * @deprecated: Use {@link #processSrcfile(File, String, String)}
   */
  @Deprecated public InfoFileDependencies processSrcfile(File fileSrc, String sLocalPathName, String sObjExt) 
  {
    return processSrcfile(fileSrc, sLocalPathName);
  }
  
  /**Check of one source file for newly against the associated result file of translation (object-file for C-compilation). 
   * @param fileSrc The source file itself
   * @param objDeps Accumulator for the dependencies of the top-level-file. This instance is created in the first level
   *                of recursion. It is filled while all depending files are processed.
   * @param recursiveCt A recursion counter. This method may be called recursively to check depending files in recursion.
   * @return Instance for this file, where dependency information are stored for further occurrence
   *         of the same file. Then the file is processed already.
   */
  InfoFileDependencies processSrcfile(File fileSrc, final ObjectFileDeps objDeps, int recursiveCt, Map<String, String> idxOnce) 
  {
    InfoFileDependencies infoFile;
    if(recursiveCt >=99){
      throw new IllegalArgumentException("CheckDeps - recursion to deep;" + fileSrc.getAbsolutePath());
    }
    try{
      String sFileSrcGenAbs = FileSystem.getCanonicalPath(fileSrc);
      String sLocalPath = cfgData.checkIsInSourcePool(sFileSrcGenAbs);
      //boolean needTranslation;
      final File fileSrcMirror = getFileSrcMirror(sLocalPath);
      //final File fileDeps = getFileDependencies(sLocalPath);
      //
      //create info for this file.
      
      String sPathSrcCanonical = FileSystem.getCanonicalPath(fileSrc);
      infoFile = checkData.indexAllInclFilesAbsPath.get(sPathSrcCanonical);
      if(infoFile != null){
        //it is checked already.
      } else {
        infoFile = checkDependenciesInputDepFile(sPathSrcCanonical, objDeps, recursiveCt+1, idxOnce);
        if(infoFile == null){
          //dependency file not found. Build it while checking sources.
          infoFile = checkSource(fileSrc, sFileSrcGenAbs, fileSrcMirror
            , null, objDeps, recursiveCt+1, idxOnce);
          String sNewly = infoFile.isNewlyItself() ? "newly; " : 
                          infoFile.isNewlyOrIncludedNewly() ? " include newly; " : "not changed; ";
          console.reportln(MainCmdLogging_ifc.fineInfo, "CheckDeps - source file checked; " + sNewly + sFileSrcGenAbs + "");
          
        } 
        checkData.indexAllInclFilesAbsPath.put(infoFile.sAbsolutePath, infoFile);
      }
    } catch(Exception exc){
      System.err.printf("CheckDependencyFile - any exception; %s\n", exc.getMessage());
      infoFile = null;
    }
    return infoFile;
  }
  
  
  InfoFileDependencies checkDependenciesInputDepFile(String sPathSrcCanonical, ObjectFileDeps objDeps, int nRecursion, Map<String, String> idxOnce)
  {
    if(nRecursion > 99){
      throw new IllegalArgumentException("CheckDeps - recursion to deep;" + sPathSrcCanonical);
    }
    if(sPathSrcCanonical.contains("PLATFORM_os_time.h"))
      stop();
    InfoFileDependencies info = checkData.indexInfoInput.get(sPathSrcCanonical);
    if(info !=null){
      //if the file is found in the indexInfoInput, it is tested already that the file is actual.
      //The info about included files comes from the dependency file. Because the file ie actual,
      //that info is actual too. But the included files are not checked yet.
      //do it:
      for(Map.Entry<String,InfoFileDependencies> includedEntry: info.includedPrimaryDeps.entrySet()){
        InfoFileDependencies including = includedEntry.getValue();
        //check all included files. 
        String sAbsName = including.sAbsolutePath;
        if(sAbsName.contains("Fw_Exception.h"))
          stop();
        InfoFileDependencies includingReal = checkData.indexAllInclFilesAbsPath.get(sAbsName);
        if(includingReal !=null){
          //file checked already:
        } else {
          //put a dummy (empty) instance to the index, to prevent circular including.
          //In C either guards or pragma once is used to prevent circular including.
          //TODO experience: checkData.indexAllInclFilesAbsPath.put(sAbsName, new InfoFileDependencies(sAbsName, console));
          
          includingReal = checkDependenciesInputDepFile(sAbsName, objDeps, nRecursion +1, idxOnce);
          if(includingReal == null){
            //The file isn't found in the input dependencies. It means either, it is unknown up to now
            //or it means, it is changed. Changed files are not stored in the indexInfoInput.
            //Therefore check the file directly. Compare source with mirror.
            File fileSrc = new File(sAbsName);
            if(fileSrc.exists()){
              includingReal = processSrcfile(fileSrc, objDeps, nRecursion +1, idxOnce);
            } else {
              //The source file from dependency tree of another file isn't found. 
              //It means either, the file is necessary but lost, or the file isn't necessary further more.
              includingReal = null; 
            }
          }
          if(includingReal !=null){
            checkData.indexAllInclFilesAbsPath.put(sAbsName, includingReal);
          }
        }
        if(includingReal == null){
          console.writeError("CheckDeps - source file not found; " + sAbsName);
        } else {
          info.addDependency(includingReal, objDeps);
        }
      }
      //remove because it is processed.
      info.includedPrimaryDeps.clear();  
      checkData.indexInfoInput.remove(sPathSrcCanonical);
    }
    return info;
  }
  
  
  
  /**Checks the File for equality and process all include statements, 
   * process recursively all included files.
   * calls:
   * <ul>
   * <li>{@link #processIncludeLine(String, String, InfoFileDependencies, ObjectFileDeps, TreeMap, String, int)}
   * </ul>
   * @param fileSrcGen The sourcefile, may be re-generated without changed substantiate content.
   * @param sFileSrgGenAbs The absolute path of the sourcefile, how used in the indices.
   * @param fileSrcMirror The sourcefile for build process, the content will be compared with fileSrcGen.
   *                  It may be null, then the no comparison is executed, only dependency evaluation.
   * @param objDeps This instance is created in the level of processing the c-file, where an obj-file
   *                is associated. In deeper levels the dependencies where noted there.
   *                Last not least in the c-file-level it will be checked whether the obj-file is to delete.
   * @param infoFile Container for dependencies for this file. The container was created from the caller.
   *                 It is only for this file. It accumulates all dependencies.
   * @param sDirCurrentFile The directory of the fileSrcGen, to support include "relativePath"
   * @param checkedFiles List of all checked included files to prevent checking twice.
   * @param recursion recursion counter.
   * @return true if any of a include file is changed in respect to the last taken build files.
   */
  InfoFileDependencies checkSource(File fileSrcGen, String sFileSrgGenAbs
      , File fileSrcMirror
      , TreeMap<String, File> checkedFiles
       , ObjectFileDeps objDeps
       , int recursion, Map<String, String> idxOnce){
    int nEqual;
    long timestampSrcNewest = 0;
    final String sFileSrcGenName = fileSrcGen.getName();
    String sLineSrc = null, sLineMirror = null;
    NextCodeLine codeLineSrc = new NextCodeLine(fileSrcGen);
    final NextCodeLine codeLineMirror;
    InfoFileDependencies infoDepsOfFile = new InfoFileDependencies(sFileSrgGenAbs, fileSrcGen
      , fileSrcMirror, fileSrcMirror !=null, console);
    if(fileSrcMirror ==null || !fileSrcMirror.exists()){
      codeLineMirror = null;
      nEqual = 0;
    } else {
      codeLineMirror = new NextCodeLine(fileSrcMirror);
      nEqual = 2;  //presume lines are equal
    }
    console.reportln(MainCmdLogging_ifc.debug, "checkFile; " + sFileSrgGenAbs);
    if(sFileSrgGenAbs.contains("ObjectJc.h"))
      stop();
    
    final String sDirCurrentFile = FileSystem.getCanonicalPath(fileSrcGen.getParentFile());
    do{
      sLineSrc = codeLineSrc.nextCodeLine();   //skips over all empty lines, forex lines which are commented with //
      if(codeLineMirror !=null){
        sLineMirror = codeLineMirror.nextCodeLine();
      }
      if(sLineMirror !=null && sLineMirror.contains("buildValidityForDataItems"))
        stop();
      if(sLineSrc != null){
        if(true){
          if(sLineSrc.contains("#include")){
            try{
              //
              //=================>>processIncludeLine()
              //
              long timestampSrcNewestInner = 0;
              //InfoFileDependencies infoFileIncl =  
              processIncludeLine(sDirCurrentFile, sLineSrc
                , infoDepsOfFile, objDeps, checkedFiles, sFileSrgGenAbs, recursion, idxOnce);
              //bChanged |= bChangedInner;
              boolean bCopiedRecursive = false; 
              //objDeps.checkedNewer(timestampSrcNewestInner, sSrcFilePathAbs);
              if(timestampSrcNewestInner > timestampSrcNewest){ 
                timestampSrcNewest = timestampSrcNewestInner;
              }
              if(bCopiedRecursive && nEqual > 1){
                nEqual = 1;  //build the dep-file newly.
              }
            } catch(IOException exc){ throw new RuntimeException(exc); 
            }
          }
        }
        if(nEqual > 0){ //if not equal, let it not equal!
          if(sLineMirror != null && !sLineMirror.equals(sLineSrc)){
            nEqual = 0;
            console.reportln(MainCmdLogging_ifc.debug, "CheckFile - changed line; " + sLineSrc);
          }
        }
        if(nEqual ==0)
          stop();
      }
    } while(sLineSrc != null); //this.args.evalDeps));
    codeLineSrc.close();
    if(codeLineMirror !=null){ codeLineMirror.close(); }
    
    final long timestampSrc;
    if(fileSrcMirror ==null){
      //no mirror file and no dependency file or changed date against dependency file entry: This file is newly.
      infoDepsOfFile.notifyNewly(objDeps);
      /*if(objDeps.checkedNewer(infoDepsOfFile, console)){
        checkData.nrofDelObj +=1;
        }
      */
      timestampSrc = fileSrcGen.lastModified();
    }
    else if(nEqual == 0){
      //not equal, copy the file
      console.reportln(MainCmdLogging_ifc.info, "CheckDeps - changed file; " + sFileSrcGenName + ";   PATH: "+ sFileSrgGenAbs);
      
      copyToMirror(fileSrcGen, fileSrcMirror, "; !!checkFile(): ");
      infoDepsOfFile.notifyNewly(objDeps);
      /*if(objDeps.checkedNewer(infoDepsOfFile, console)){
        checkData.nrofDelObj +=1;
      }
      */
      timestampSrc = fileSrcGen.lastModified();
    } else {
      infoDepsOfFile.notifyChangedTimestamp();
      timestampSrc = fileSrcMirror.lastModified();
    }
    //don't check timestamp! Only changed files are causes for new translation:
    //objDeps.checkedNewer(timestampSrc, sFileSrgGenAbs);
    
    if(timestampSrcNewest < timestampSrc){
      timestampSrcNewest = timestampSrc;   //the copied file determines the newest timestamp.
    }
    if(console.getReportLevel() >= MainCmdLogging_ifc.debug){
      console.reportln(MainCmdLogging_ifc.debug, "checkFile finished; " + sFileSrgGenAbs);
      reportIncludesOfFile(infoDepsOfFile, 1, new TreeMap<String, InfoFileDependencies>());
    }
    /*
    if(checkData.writerDepAll !=null){
      try{ infoDepsOfFile.writeDependendingFiles(checkData.writerDepAll, new TreeMap<String, String>(), console, 0); }
      catch(IOException exc){ throw new RuntimeException(exc); }
    }
    */
    return infoDepsOfFile; //timestampSrcNewest;  //to decide for the file which includes the checked one.
  }



  
  
  
  
  
  /**Processes a line which contains an #include-statement. Checks whether the file is processed already.
   * Process it. 
   * <ul>
   * <li>The included file is extracted, in "file" or < file>.
   * <li>The included file is searched in {@link #indexAllInclFilesShortPath}.
   *   If it is found there, the file is processed already. Its {@link InfoFileDependencies}
   *   are known already. The timestamp of the file is known, it is compared with the Obj-file-timestamp
   *   calling {@link ObjectFileDeps#checkedNewer(long, String)}.
   * <li>If the file isn't found with its relative path, the file is searched in the include-path
   *   given in {@link #listInclPaths}. If the file isn't found, an error message is outputted.
   *   Then the include path should be corrected.
   * <li>If the file is found in the include path, its absolute path is known yet. The the file is searched
   *   in the {@link #indexAllInclFilesAbsPath}. It is possible that the same file is included
   *   with different relative pathes, but it is the same absolute path. If the file is found there,
   *   it is processed already. Then the same procedure is done if it was found in the relative path.    
   * </ul>
   * It calls
   * <ul>
   * <li>{@link #processSrcfile(File, ObjectFileDeps, int)}
   *   if the included file isn't processed yet and it is a generated source.
   *   There checkFile or {@link #checkDependencyFile(File, ObjectFileDeps, InfoFileDependencies, int)}
   *   will be called.
   * <li>{@link #checkFile(File, String, File, ObjectFileDeps, InfoFileDependencies, String, TreeMap, int)}
   *   if the include file isn't processed yet and it is a external source.
   * <li>{@link #checkDependencyFile(File, ObjectFileDeps, InfoFileDependencies, int)}
   *   if the include file isn't processed yet and the dependency file exists and is not older.
   *   NOTE: This is not done yet, because there isn't a concept of saving the dependency file
   *   for external files. Only for generated sources it is given. Idea: One file for all dependencies.
   * <li>nothing, if the file is processed already. Then the informations are contained
   *   in the found {@link InfoFileDependencies} in {@link #indexAllInclFilesAbsPath}.  
   * <ul>    
   * @param sDirCurrentFile Absolute path of the current file, to support relative include
   * @param sLine The line with #include -statement
   * @param infoFileParent The container of dependencies of the calling level.
   * @param objDeps This instance is created in the level of processing the c-file, where an obj-file
   *                is associated. In deeper levels the dependencies where noted there.
   *                Last not least in the c-file-level it will be checked whether the obj-file is to delete.
   * @param checkedFiles index of all checked files of this 
   * @param sSrcPath The path of the source of calling level (the file which contains the include-line),
   *                 only for report 
   * @param recursion recursion count. 
   * @throws FileNotFoundException
   */
  void processIncludeLine(String sDirCurrentFile, String sLine
      , AddDependency_InfoFileDependencies infoFileParent
      , ObjectFileDeps objDeps
      , TreeMap<String, File> checkedFiles
      , String sSrcPath, int recursion, Map<String, String> idxOnce) 
  throws FileNotFoundException {
    //System.out.println(sLine);
    //long timestampSrcNewest = 0;
    int from,to;
    final boolean includeFromCurrent;
    from = sLine.indexOf('<');
    if(from <0){
      includeFromCurrent = true;
      from = sLine.indexOf('\"');
      to = sLine.indexOf('\"',from+1);
    } else {
      includeFromCurrent = false;
      to = sLine.indexOf('>');
    }
    int posLineComment = sLine.indexOf("//");
    InfoFileDependencies inclFileInfo;
    if(posLineComment >=0 && posLineComment < from){
      //commented include
      inclFileInfo = null;
    } else if(from < 0 || to < 0 || from >= to){
      console.writeError("processIncludeLine - syntax error in include line; " + sLine + "|||File: " + sSrcPath);
      inclFileInfo = null;
    } else {
      String sFileIncl = sLine.substring(from+1, to);
      console.reportln(MainCmdLogging_ifc.debug, "processIncludeLine - included File; " + sFileIncl);
      if(sLine.contains("../2src/reflection_offset.crefl"))
        stop();
      
      //is the file checked already? Search it in the indexes of short and absolute paths:
      if(includeFromCurrent){ //The include statement is written in "file.h"
        File fileIncl = new File(sDirCurrentFile + "/" + sFileIncl);
        if(fileIncl.exists()){ 
          //A file relative to the current file is found. It means, that is included.
          //Get its absolute path in check whether it is processed already:
          String sInclFileAbs = FileSystem.getCanonicalPath(fileIncl);
          inclFileInfo = checkData.indexAllInclFilesAbsPath.get(sInclFileAbs);
        } else {
          //because the included file isn't found relative, it may be assumed 
          //that it should be written in <path/file.h> but it is written usual in "path/file.h".
          //The compiler searches the included file in its include paths. The first place of discovery wins.
          //In the same manner the file can be searched in the index of short paths whether it is processed already:
          inclFileInfo = checkData.indexAllInclFilesShortPath.get(sFileIncl);
        } 
      } else { //!includeFromCurrent
        //search the file with its given short paths. The compiler searches it in the same manner.
        //It assumes that the same include statement in several sources refers to the same file.
        inclFileInfo = checkData.indexAllInclFilesShortPath.get(sFileIncl);
      }
      //=>inclFileInfo may be found or not:
      //
      if(inclFileInfo == null){ //file isn't processed yet, or it is a includeFromCurrent
        char[] typeInclude = new char[1];
        //search the file in the include path. This method searches first from the current dir 
        //if includeFromCurrent == true. That is a cheap operation. 
        //If the file isn't found relative, the include path is used.
        //The include path is related to the original source files, because the mirror files doesn't may existing.
        File fileIncl = searchInclFileInIncludePath(sFileIncl, sDirCurrentFile, includeFromCurrent, typeInclude);
        if(typeInclude[0] != 'y'){  //do not process system include files.
          final String sInclFileAbs;
          if(fileIncl == null){
            //file not found. The compiler doesn't it found too, if the include paths are correct.
            //Only write a warning. Don't handle the file. Process the others.
            console.writeInfoln("searchInclude - file not found: " + sFileIncl + "|||File: " + sSrcPath);
            //no: throw new IllegalArgumentException("searchInclude - file not found: " + sFileIncl);
          } else {
            //The file is found. Save it in the index of processed files. Fill content then.
            sInclFileAbs = FileSystem.getCanonicalPath(fileIncl);
            //The fileInfo may be found with its absolute path. It is if different #include <path/name.h>-notations are used.
            inclFileInfo = checkData.indexAllInclFilesAbsPath.get(sInclFileAbs);
            if(inclFileInfo == null){ //not found yet
              if(idxOnce.get(sInclFileAbs) == null){
                //
                idxOnce.put(sInclFileAbs, sInclFileAbs);  //prevent multiple include, only once. It is like guards in C
                //
                //This file is included, is contained in the source-pool or not, and it s not checked yet.
                //Check it, build the InfoFileDependencies, maybe recursively call of that routine.
                inclFileInfo = processSrcfile(fileIncl, objDeps, recursion, idxOnce);
              }
            }
          }
        }
        //store the include-file-string to prevent newer search with the same include path.
        //inclFileInfo is null if the included file isn't found.
        if(typeInclude[0] !='r'){  //not a file found relative to the source:
          //register it in the index of short paths.
          checkData.indexAllInclFilesShortPath.put(sFileIncl, inclFileInfo);
        }
      }
      //
      if(inclFileInfo !=null){
        infoFileParent.addDependency(inclFileInfo, objDeps);
        /*if(objDeps.checkedNewer(inclFileInfo, console)){
          checkData.nrofDelObj +=1;
        }
        */
      }
      
    }
    //return inclFileInfo; //timestampSrcNewest; //hasCopied;
  }
  
  

 
  
  
  
  
  /**Searches the file in the include path respectively from the current file.
   * There is a system-include-path-list {@link #listSystemInclPaths}
   * and a user include path list {@link #listInclPaths}.
   * @param sPathInIncludeLine The file name maybe with a path from the include statement.
   * @param sDirCurrentFile The directory of the current file to search from there.
   * @param includeFromCurrent true if it is a #include "file.h"-statement. false if it is a #include <file.h>-statement.
   * @param typeInclude Return char
   * @return The found include file in the include path or null.
   */
  File searchInclFileInIncludePath(String sPathInIncludeLine 
    , String sDirCurrentFile, boolean includeFromCurrent, char[] typeInclude
  ){
    //boolean isSystemFile = false;
    File fileIncl = null;
    //first search starting in the current file if it is an ""-include
    if(includeFromCurrent && sDirCurrentFile != null){
      fileIncl = new File(sDirCurrentFile + "/" + sPathInIncludeLine);
      if(!fileIncl.exists()){ fileIncl = null; }
      else { typeInclude[0] = 'r';
      }
    }
    if(fileIncl == null){
      typeInclude[0] = 'y';
      fileIncl = searchInclFileInIncludePath(sPathInIncludeLine, cfgData.listSystemInclPaths);
    }
    if(fileIncl == null){
      typeInclude[0] = 'g';
      fileIncl = searchInclFileInIncludePath(sPathInIncludeLine, cfgData.listGenSrcInclPaths);
    }   
    if(fileIncl == null){
      typeInclude[0] = 'i';
      fileIncl = searchInclFileInIncludePath(sPathInIncludeLine, cfgData.listInclPaths);
    }   
    return fileIncl;
  }
  

  
 
  
  File searchInclFileInIncludePath(String sPathInIncludeLine, List<File> src){
    Iterator<File> iterInclPaths = src.iterator();
    File fileIncl = null;
    while(fileIncl ==null && iterInclPaths.hasNext()){
      File directory = iterInclPaths.next();
      fileIncl = new File(directory, sPathInIncludeLine);
      if(!fileIncl.exists()){
        fileIncl = null;  //invalid.
      }
    }
    return fileIncl;
  }
  

  


  /**Copies the original source file to the build box, saves the last file if such parameters are given.
   * @param fileSrcGen The original
   * @param fileSrcMirror The copy
   */
  void copyToMirror(File fileSrcGen, File fileSrcMirror, String sTest)
  {
    if(fileSrcMirror.exists()){
      //rename the existing file, so a comparison may be done afterward.
      if(true){  //rename to .last in current dir:
        boolean bOk;  
        File fileBack = new File(fileSrcMirror.getAbsolutePath() + ".last");
        if(fileBack.exists()){
          if(!fileBack.canWrite()){ 
            bOk = fileBack.setWritable(true);
            if(!bOk){ 
              console.reportln(MainCmdLogging_ifc.info, sTest + "CheckDeps - ERROR set writeable " + fileBack.getAbsoluteFile());
            }
          }
          bOk = fileBack.delete();
          if(!bOk){ 
            console.reportln(MainCmdLogging_ifc.info, sTest + "CheckDeps - ERROR delete " + fileBack.getAbsoluteFile());
          }
        }
        bOk = fileSrcMirror.renameTo(fileBack);  //rename may doesn't work!  
        if(bOk) { console.reportln(MainCmdLogging_ifc.fineInfo, "checkDeps - rename to; " + fileBack.getAbsoluteFile() + sTest); }
        else { 
          console.reportln(MainCmdLogging_ifc.debug, sTest + "CheckDeps - ERROR rename to " + fileBack.getAbsoluteFile());
          //because renaming isn't possible, try copy and delete.
          try{
            bOk = fileSrcMirror.delete();
            if(!bOk){ console.reportln(MainCmdLogging_ifc.fineInfo, "CheckDeps - delete fails; " + fileBack.getAbsoluteFile() + sTest); }
            FileSystem.mkDirPath(fileBack);
            FileSystem.copyFile(fileSrcMirror, fileBack);
            bOk = fileSrcMirror.delete();
            if(!bOk){ console.reportln(MainCmdLogging_ifc.fineInfo, "CheckDeps - delete fails; " + fileBack.getAbsoluteFile() + sTest); }
          } catch(IOException exc){
            console.writeError(sTest + "CheckDeps - problem copy to lastpath; file=" 
              + fileBack.getAbsolutePath() + "; srcFile=" + fileSrcMirror.getAbsolutePath());
          }
        }
      }
    } else {  //!fileSrcMirror.exists()
      try{ FileSystem.mkDirPath(fileSrcMirror); }
      catch(IOException exc){
        console.writeError(sTest + "CheckDeps_C - error mkDirPath; file=" 
          + fileSrcMirror.getAbsolutePath());
      }
    }
    try{ FileSystem.copyFile(fileSrcGen, fileSrcMirror);}
    catch(IOException exc){
      console.writeError(sTest + "CheckDeps_C - can't copy build-source-file: " 
        + fileSrcGen.getAbsolutePath());
    }

  }
    
  
  
  
  void reportIncludesOfFile(InfoFileDependencies infoFile, int recursionCt, Map<String, InfoFileDependencies> indexMainCmdLogging_ifced)
  {
    for(Map.Entry<String, InfoFileDependencies> entry: infoFile.includedPrimaryFiles.entrySet()){
      InfoFileDependencies info = entry.getValue();
      if(info !=null){
        console.reportln(MainCmdLogging_ifc.debug, "  checkFile includes; " + recursionCt + "; "+ info.sFilePathIncludeline);
        if(info.fileSrc !=null){  //maybe includepath not found.
          String sFileAbsPath = info.fileSrc.getAbsolutePath(); 
          if(indexMainCmdLogging_ifced.get(sFileAbsPath) == null){
            indexMainCmdLogging_ifced.put(sFileAbsPath, info);
            if( info.includedPrimaryFiles.size() >0 && recursionCt < 99){
              reportIncludesOfFile(info, recursionCt +1, indexMainCmdLogging_ifced);
            }
          } else {
            stop();
            //console.reportln(MainCmdLogging_ifc.info, "  recursion included:" + sFileAbsPath);  
          }
        }
      } else {
        console.reportln(MainCmdLogging_ifc.debug, "  checkFile includes; " + recursionCt + "; Systemfile; "+ entry.getKey());
      }
    }
  }
  
  
  
  
  
  
  
  
  void deleteResultFile(String localName){
    
  }
  
  
  
  /**Writes the dependencies which are checked after creation of this class in the given file.
   * @param sFileDeps The file to write into.
   * @return
   */
  public String writeDependencies(String sFileDependencies){
    InfoFileDependencies.writeAllBackDeps(sFileDependencies, checkData.indexAllInclFilesAbsPath);
    return "";
  }
  
  
  
  
  /**Writes the dependencies which are checked after creation of this class in the given file.
   * @param sFileDeps The file to write into.
   * @return
   */
  public String writeDependencies(){
    InfoFileDependencies.writeAllBackDeps(sFileDeps, checkData.indexAllInclFilesAbsPath);
    return "";
  }
  
  
  public void close(){
    
  }
  
  
  void stop(){
  }
  
  
  
  /**Helper class to get the next line in a source file without comment. */
  private static class NextCodeLine
  {
    boolean lastWasCommentStart;
    
    BufferedReader reader;
    
    NextCodeLine(File file){
      try{
        reader = new BufferedReader(new FileReader(file));
      } catch(FileNotFoundException exc){ throw new RuntimeException("unexpected: " + exc.getMessage()); }
    }
    
    String nextCodeLine(){
      String sLine = null;
      boolean repeatSearchCommentEnd;
      do{
        repeatSearchCommentEnd = false; //set if necessary.
        try{ sLine = reader.readLine(); }
        catch(IOException exc){ throw new RuntimeException("unexpected"); }
        if(sLine !=null){
          if(sLine.contains("buildValidityForDataItems"))
            stop();

          int posLineComment = sLine.indexOf("//");
          if(posLineComment >=0){ sLine = sLine.substring(0, posLineComment); }
          if(lastWasCommentStart){
            //the last line has contained a comment start, now check whether */ is found.
            int posCommentEnd = sLine.indexOf("*/");
            if(posCommentEnd <0){ //no */ found
              repeatSearchCommentEnd = true;
            } else {
              lastWasCommentStart = false;
              sLine = sLine.substring(posCommentEnd + 2);
            }
          }  
          if(!lastWasCommentStart) {  //NOTE: extra test of /*, may be situation ...*/  xx /*
            //normal line, check if the line starts a comment over more as one line.
            int posComment;
            boolean repeatComment;
            do {
              if((posComment= sLine.indexOf("/*"))>=0){
                //A /* is found, now check whether the comment is closed in the same line:
                //int posCommentEnd = sLine.substring(0, posComment).lastIndexOf("*/");
                int posCommentEnd = sLine.indexOf("*/", posComment);
                if(posCommentEnd < posComment){  //not found or */ after last /*
                  //it is a line which starts with a /* for a line-block-comment:
                  repeatSearchCommentEnd = false;  //remain false, set true in the next line. because start of line shoule be returned
                  lastWasCommentStart = true;
                  sLine = sLine.substring(0, posComment);
                  repeatComment = false;
                } else {
                  //the line contains a inline comment. remove it. Don't compare it.
                  sLine = sLine.substring(0, posComment) + sLine.substring(posCommentEnd+2);
                  repeatComment = true;
                }
              } else {
                repeatComment = false;
              }
            } while(repeatComment);
          }
        }
        if(!repeatSearchCommentEnd && sLine !=null){
          //detect whether its an empty line. 
          //Second is approach: ignore leading and trailing spaces for comparison
          sLine = sLine.trim();  
        }
      } while(sLine != null && (repeatSearchCommentEnd || sLine.length() == 0));
      return sLine;
    }
    
    void close()
    {
      try{ reader.close(); }
      catch(IOException exc){ throw new RuntimeException(exc); }
    }
    
    
    void stop(){}
  }//class NextCodeLine
  
  


}




