context: using System;
  using System.Collections.Generic;
  using System.Text;
  using System.Xml;
  using System.IO; 
  
  namespace HycalperConsole {
      public class HycalperConsole {
  
          char[] WHITESPACE = new char[4] { '\n', ' ', '\t','\r' };
          public static readonly string LOOPSTART = "#region HYCALPER LOOPSTART";
          public static readonly string LOOPEND = "#endregion HYCALPER LOOPEND";
          public static readonly string TYPELIST = "!HC:TYPELIST:";
          public static readonly string ENUMPATTERN = "!HC:ENUM:";
          public static readonly string ENUMPATTERNEND = "!HC:/ENUM";
          public static readonly string EXCLUDEPATTERN = "HCEXCLUDE";
          public static readonly string AUTOGENERATEDSTART = "#region HYCALPER AUTO GENERATED CODE";
          public static readonly string AUTOGENERATEDEND = "#endregion HYCALPER AUTO GENERATED CODE";
          // if no "endmark" attribute is found for a type, the region 
          // is expected to run, until one of these chars ist found: 
          public static readonly string DEFAULT_ENDREGION_CHARS = "> ,();\n\t\r*[]";  
  
  
          private readonly bool include_secure = true; // enclose enums into CDATA tags 
          private readonly bool parenthesize = true; // enclose auto generated source into #region DESC and #endregion tags 
          private readonly bool comment = true;  // comment will be included before first namespace directive (if both was found)
          private readonly bool remove_comments = true;  // all comments beginning with '/*!HC' will be removed from output
                                                         // (this may conflict with 'keep_tags' and has precedence!)
          private readonly bool keep_tags = false; // all hycalper marks will stay in resulting code [false]
          private readonly bool skip_uptodate = true; // only recalp changed files 
  
          XmlDocument m_xmlDoc; 
  
          private string m_saveFilename = "";
          public string SaveFilename {
              get {
                  return m_saveFilename;
              }
          }
          private Types m_pattern;
          public Types Pattern {
              get {
                  return m_pattern;
              }
          }
          private int m_error = 0;
          public int Error {
              get {
                  return m_error;
              }
          }
          private string m_message = "";
          public string Message {
              get {
                  return m_message;
              }
          }
          private int m_enums = 0;
          public int Enums {
              get {
                  return m_enums;
              }
          }
          private int m_loops = 0;
          public int Loops {
              get {
                  return m_loops;
              }
          }
          private string m_targetPath = "";
  
          private int m_linesInsideHCTags = 0;
          public int LinesInsideHCTags {
              get { return m_linesInsideHCTags; }
              set { m_linesInsideHCTags = value; }
          }
  
          private int m_linesGenerated = 0;
          public int LinesGenerated {
              get { return m_linesGenerated; }
              set { m_linesGenerated = value; }
          }
  
          private int m_linesGlobal = 0;
          public int LinesGlobal {
              get { return m_linesGlobal; }
          } 
          public HycalperConsole(string filename, string targetDir) {
              try {
                  m_targetPath = targetDir;
                  int curPosition = 0;
                  bool write2same_file = false; 
                  string input = System.IO.File.ReadAllText(filename);
                  m_linesGlobal = countLines(input); 
                  if (input.Length > 0) {
                      // read xml types from input file 
                      int lenXML = 0;
                      m_pattern = readTypesXML(ref input, out lenXML, ref m_xmlDoc, false);
                      if (lenXML == 0) {
                          m_message = "There is nothing to hycalpe here!";
                          m_error = 3;
                          return;
                      }
                      if (m_pattern == null) throw new Exception("Invalid XML Types definition.");
                      // extract outfile
                      XmlNode outFileNameNode = m_xmlDoc.GetElementsByTagName ( "outfile" ).Item ( 0 );
                      if (outFileNameNode != null && outFileNameNode.InnerText != null
                          && outFileNameNode.InnerText.Length > 0) {
                          m_saveFilename = Path.Combine(Path.GetDirectoryName(filename)
                                                             , outFileNameNode.InnerText);
                          if (m_saveFilename == null || m_saveFilename.Length == 0)
                              throw new Exception("Unable to find / extract outfile specification.");
                      } else {
                          // save to self
                          m_saveFilename = filename;
                          write2same_file = true;
                      }
                      // skip if uptodate 
                      if (skip_uptodate && !write2same_file) {
                          DateTime lastAccessOutfile = File.GetLastWriteTime ( m_saveFilename );
                          DateTime lastAccessSource = File.GetLastWriteTime ( filename );
                          if (lastAccessOutfile > lastAccessSource) {
                              // skip 
                              m_message = "...uptodate -> skipped.";
                              m_error = 2; 
                              return; 
                          }
                      }
                      // extract + process loops 
                      curPosition = input.IndexOf(LOOPSTART);
                      int appendIdx = curPosition;
                      while (curPosition >= 0) {
                          int [] excludePattern; 
                          Types localPattern; 
                          string loopSequence = extractLoop(input, ref curPosition, out localPattern, out excludePattern);
                          if (loopSequence != null) {
                              if (localPattern == null) localPattern = m_pattern;
                              System.Array.Sort(excludePattern);
                              appendIdx = beginOfLine(input, curPosition);
                              // if old marks -> remove content between them 
                              if (write2same_file) {
                                  int startIdx = input.Substring(0, endOfLine(input, appendIdx)).IndexOf(AUTOGENERATEDSTART, appendIdx);
                                  if (startIdx >= 0) {
                                      int endIdx = nextNewLine(input, input.IndexOf(AUTOGENERATEDEND, startIdx));
                                      if (endIdx > startIdx) {
                                          input = input.Remove(appendIdx, endIdx - appendIdx);
                                      }
                                  }
                              }
                              // create new region marks
                              if (parenthesize || write2same_file) {
                                  input = input.Insert(appendIdx, Environment.NewLine + AUTOGENERATEDEND
                                                                  + Environment.NewLine);
                              }
                              int curExcludingIdx = 0;
                              for (int i = 0; i < localPattern.CountPattern; i++) {
                                  // replace only pattern which are not excluded
                                  if (curExcludingIdx >= excludePattern.Length || excludePattern[curExcludingIdx] != i) {
                                      int tagsReplaced = 0;
                                      string insertString = replaceAllKeys(loopSequence, localPattern, i, ref tagsReplaced);
                                      insertString = removeCommentStubs(insertString, "/*!HC", "*/");
                                      // insert only, if something was replaced
                                      if (tagsReplaced > 0) {
                                          input = input.Insert(appendIdx, insertString);
                                          curPosition += insertString.Length;
                                      }
                                  } else {
                                      curExcludingIdx++;
                                  }
                              }
                              if (parenthesize || write2same_file) {
                                  string endMark = AUTOGENERATEDSTART + Environment.NewLine;
                                  if (write2same_file)
                                      endMark = endMark + "// DO NOT EDIT INSIDE THIS REGION !! CHANGES WILL BE LOST !! "
                                                        + Environment.NewLine;
                                  input = input.Insert(appendIdx, endMark);
                                  curPosition += endMark.Length;
                              }
                              m_loops++;
                          } else {
                              throw new Exception("Error extracting loop sequence: " + m_message);
                          }
                          curPosition = input.IndexOf(LOOPSTART, curPosition + 1);
                      }
                      // find and process enums 
                      curPosition = input.IndexOf(ENUMPATTERN, 0); 
                      while (curPosition >= 0) {
                          int startIdx = input.IndexOfAny(WHITESPACE,curPosition);
                          int startKeyIdx = curPosition + ENUMPATTERN.Length;
                          int endKeyIdx = input.IndexOfAny(new char[11] { ':', ' ', ',', '*', '/', '(', ')', '}', '{', '\r', '\n' }, startKeyIdx);
                          appendIdx = endOfWord(input,endKeyIdx);
                          if (endKeyIdx > startKeyIdx) {
                              string key = input.Substring(startKeyIdx, endKeyIdx - startKeyIdx).Trim();
                              string insertString = enumAllKeyPatterns(key);
                              endKeyIdx = input.IndexOf(ENUMPATTERNEND, endKeyIdx);
                              if (endKeyIdx < 0 )
                                  throw new Exception("Missing enum end tag!");
                              endKeyIdx = beginOfWord(input,endKeyIdx); 
                              input = input.Remove(appendIdx, endKeyIdx - appendIdx); 
                              input = input.Insert(appendIdx, " " + insertString + " ");
                              curPosition += insertString.Length;
                              m_enums++;
                          } else {
                              throw new Exception("Missing class spec in enum.");
                          }
                          curPosition = input.IndexOf(ENUMPATTERN, curPosition + 1); 
                      }
                      // Comment 
                      if (comment && input.IndexOf("namespace") > 2) {
                          string commentString;
                          if (write2same_file) {
                              commentString =         "//////////////////////////////////////////////////////////////////"
                              + Environment.NewLine + "//                                                              //"
                              + Environment.NewLine + "//  This is an auto - manipulated source file.                  //"
                              + Environment.NewLine + "//  Edits inside regions of HYCALPER AUTO GENERATED CODE        //"
                              + Environment.NewLine + "//  will be lost and overwritten on the next build!             //"
                              + Environment.NewLine + "//                                                              //"
                              + Environment.NewLine + "//////////////////////////////////////////////////////////////////"
                              + Environment.NewLine;
                          } else {
                              commentString =         "//////////////////////////////////////////////////////////////////"
                              + Environment.NewLine + "//  This is an auto - generated source file.                    //"
                              + Environment.NewLine + "//  Do not manually edit this file! Any changes made will be    //"
                              + Environment.NewLine + "//  lost on the next build! Edit the corresponding source file  //"
                              + Environment.NewLine + "//  (per default located in the template/ folder) instead!      //"
                              + Environment.NewLine + "//                                                              //"
                              + Environment.NewLine + "//////////////////////////////////////////////////////////////////"
                              + Environment.NewLine;
                          }
                          XmlNode commentNode = m_xmlDoc.GetElementsByTagName("comment")[0];
                          if (commentNode != null && commentNode.InnerText != null) {
                              commentString += Environment.NewLine + "///////////////////// UserComment: ///////////////////////////////"
                              + Environment.NewLine + "/*" + commentNode.InnerText + "*/";
                          }
                          if (input.IndexOf(commentString) < 0) {
                              input = input.Insert(0,commentString);
                          }
                      }
                      // write to file
                      if (m_saveFilename != null) {
                          System.IO.File.WriteAllText(m_saveFilename, input);
                      }
                      m_error = 0;    
                      return;
                  }
              } catch (Exception e) {
                  m_message = e.Message; 
                  m_error = 1; 
                  return;
              }
          }
  
          private int countLines (string input) {
              if (input == null || input.Length == 0) 
                  return 0; 
              int pos = 0,count = 0;  
              pos = input.IndexOf(Environment.NewLine); 
              while (pos < input.Length && pos >= 0) {
                  count++; 
                  pos = input.IndexOf(Environment.NewLine,pos+1); 
              }
              return count; 
          }
  
          private string removeCommentStubs(string insertString, string patternStart, string patternEnd) {
              // remove (de)comments stubs
              if (remove_comments) {
                  int startTagIdx = insertString.IndexOf(patternStart, 0);
                  while (startTagIdx >= 0) {
                      int endTagIdx = insertString.IndexOf ( patternEnd, startTagIdx );
                      if (endTagIdx > 0) {
                          endTagIdx += patternEnd.Length;
                          if (startTagIdx > 1 && insertString [startTagIdx - 2] == ' '
                              && insertString [startTagIdx - 1] == ' ')
                              startTagIdx--;
                          if (endTagIdx > startTagIdx) {
                              insertString = insertString.Remove ( startTagIdx, endTagIdx - startTagIdx );
                          }
                      }
                      startTagIdx = insertString.IndexOf(patternStart, startTagIdx + 1);
                  }
              }
              return insertString;
          }
  
          private Types readTypesXML(ref string input,out int lengthXML,  ref XmlDocument xmlDoc, bool removeText) {
              lengthXML = 0; 
              int startTagXmlIdx = input.IndexOf("<hycalper>"); //StringComparison.OrdinalIgnoreCase); 
              if (startTagXmlIdx < 0) return null; 
              int endTagXmlIdx = input.IndexOf("</hycalper>", startTagXmlIdx); //,StringComparison.OrdinalIgnoreCase);
              if (endTagXmlIdx > startTagXmlIdx) {
                  string xml = input.Substring(startTagXmlIdx, endTagXmlIdx - startTagXmlIdx + "</hycalper>".Length);
                  lengthXML = xml.Length; 
                  XmlTextReader reader = new XmlTextReader(new StringReader(xml));
                  xmlDoc = new XmlDocument();
                  xmlDoc.Load(reader);
                  // remove xml read from input string 
                  if (removeText) {
                      endTagXmlIdx = endOfLine(input, endTagXmlIdx);
                      startTagXmlIdx = beginOfLine(input, startTagXmlIdx);
                      input = input.Remove(startTagXmlIdx, endTagXmlIdx - startTagXmlIdx);
                  }
                  return new Types(xmlDoc);
              }
              return null; 
          }
  
          private string extractLoop(string input, ref int curloopstart,out Types localPattern, out int[] excludePattern) {
              string ret = null;
              excludePattern = new int [0];
              localPattern = null; 
              try {
                  int startIdx =  input.IndexOf(LOOPSTART, curloopstart) + LOOPSTART.Length;
                  startIdx = endOfWord(input,startIdx); 
                  int lineEnd = endOfLine(input,startIdx);
                  string [] parameter = input.Substring ( startIdx, lineEnd - startIdx ).Trim ().Split(' '); 
                  // handle parameter
                  bool readingFromFile = false;
                  string fileName = "";
                  string loopName = "";
  
gt:                 foreach (string param in parameter) {