context: /* Copyright (C) 1997-2007  Christoph Steinbeck <steinbeck@users.sourceforge.net>
   *
   *  Contact: cdk-devel@lists.sourceforge.net
   *
   *  This program is free software; you can redistribute it and/or
   *  modify it under the terms of the GNU Lesser General Public License
   *  as published by the Free Software Foundation; either version 2.1
   *  of the License, or (at your option) any later version.
   *  All we ask is that proper credit is given for our work, which includes
   *  - but is not limited to - adding the above copyright notice to the beginning
   *  of your source code files, and to any copyright notice that you may distribute
   *  with programs based on this work.
   *
   *  This program 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 Lesser General Public License for more details.
   *
   *  You should have received a copy of the GNU Lesser General Public License
   *  along with this program; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   */
  
  using NCDK.Common.Primitives;
  using NCDK.IO.Formats;
  using NCDK.IO.Setting;
  using NCDK.Numerics;
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Text.RegularExpressions;
  
  namespace NCDK.IO
  {
      /// <summary>
      /// Reads a molecule from the original MDL MOL or SDF file <token>cdk-cite-DAL92</token>. An SD files
      /// is read into a <see cref="IChemSequence"/> of <see cref="IChemModel"/>'s. Each ChemModel will contain one
      /// Molecule. If the MDL molfile contains a property block, the <see cref="MDLV2000Reader"/> should be
      /// used.
      /// <para>
      /// If all z coordinates are 0.0, then the xy coordinates are taken as
      /// 2D, otherwise the coordinates are read as 3D.
      /// </para>
      /// <para>
      /// The title of the MOL file is read and can be retrieved with:
      /// <code>
      ///   molecule.GetProperty&lt;string&gt;(CDKPropertyName.Title);
      /// </code>
      /// </para>
      /// </summary>
      /// <seealso cref="MDLV2000Reader"/>
      // @cdk.module io
      // @cdk.iooptions
      // @author     steinbeck
      // @author     Egon Willighagen
      // @cdk.created    2000-10-02
      // @cdk.keyword    file format, MDL molfile
      // @cdk.keyword    file format, SDF
      [Obsolete("This reader is only for molfiles without a version tag, typically the most " 
          + "common molfile now encountered is V2000 and the " + nameof(MDLV2000Reader) + " should be used "
          + "instead. The " + nameof(MDLV2000Reader) + " reader can actually read files missing the version tag when "
          + "in relaxed mode.")]
      public class MDLReader : DefaultChemObjectReader
      {
          TextReader input = null;
  
          private BooleanIOSetting forceReadAs3DCoords;
          private static readonly Regex TRAILING_SPACE = new Regex("\\s+$", RegexOptions.Compiled);
  
          /// <summary>
          ///  Constructs a new MDLReader that can read Molecule from a given Stream.
          /// </summary>
          /// <param name="input">The Stream to read from</param>
          public MDLReader(Stream input)
              : this(input, ChemObjectReaderMode.Relaxed)
          {
          }
  
          public MDLReader(Stream input, ChemObjectReaderMode mode)
              : this(new StreamReader(input))
          {
              base.ReaderMode = mode;
          }
  
          /// <summary>
          /// Constructs a new MDLReader that can read Molecule from a given Reader.
          /// </summary>
          /// <param name="input">The Reader to read from</param>
          public MDLReader(TextReader input)
              : this(input, ChemObjectReaderMode.Relaxed)
          {
          }
  
          public MDLReader(TextReader input, ChemObjectReaderMode mode)
          {
              base.ReaderMode = mode;
              this.input = input;
              InitIOSettings();
          }
  
          public override IResourceFormat Format => MDLFormat.Instance;
  
          public override bool Accepts(Type type)
          {
              if (typeof(IChemFile).IsAssignableFrom(type)) return true;
              if (typeof(IChemModel).IsAssignableFrom(type)) return true;
              if (typeof(IAtomContainer).IsAssignableFrom(type)) return true;
              return false;
          }
  
          /// <summary>
          ///  Takes an object which subclasses <see cref="IChemObject"/>, e.g. Molecule, and will read
          ///  this (from file, database, internet etc). If the specific implementation
          ///  does not support a specific <see cref="IChemObject"/> it will throw an Exception.
          /// </summary>
          /// <param name="obj">The object that subclasses <see cref="IChemObject"/></param>
          /// <returns>The IChemObject read</returns>
          /// <exception cref="CDKException"></exception>
          public override T Read<T>(T obj)
          {
              if (obj is IChemFile)
              {
                  return (T)ReadChemFile((IChemFile)obj);
              }
              else if (obj is IChemModel)
              {
                  return (T)ReadChemModel((IChemModel)obj);
              }
              else if (obj is IAtomContainer)
              {
                  return (T)ReadMolecule((IAtomContainer)obj);
              }
              else
              {
                  throw new CDKException("Only supported are ChemFile and Molecule.");
              }
          }
  
          private IChemModel ReadChemModel(IChemModel chemModel)
          {
              IChemObjectSet<IAtomContainer> setOfMolecules = chemModel.MoleculeSet;
              if (setOfMolecules == null)
              {
                  setOfMolecules = chemModel.Builder.NewAtomContainerSet();
              }
              IAtomContainer m = ReadMolecule(chemModel.Builder.NewAtomContainer());
              if (m != null)
              {
                  setOfMolecules.Add(m);
              }
              chemModel.MoleculeSet = setOfMolecules;
              return chemModel;
          }
  
          /// <summary>
          /// Read a ChemFile from a file in MDL SDF format.
          /// </summary>
          /// <returns>The ChemFile that was read from the MDL file.</returns>
          private IChemFile ReadChemFile(IChemFile chemFile)
          {
              IChemSequence chemSequence = chemFile.Builder.NewChemSequence();
  
              IChemModel chemModel = chemFile.Builder.NewChemModel();
              IChemObjectSet<IAtomContainer> setOfMolecules = chemFile.Builder.NewAtomContainerSet();
              IAtomContainer m = ReadMolecule(chemFile.Builder.NewAtomContainer());
              if (m != null)
              {
                  setOfMolecules.Add(m);
              }
              chemModel.MoleculeSet = setOfMolecules;
              chemSequence.Add(chemModel);
  
              setOfMolecules = chemFile.Builder.NewAtomContainerSet();
              chemModel = chemFile.Builder.NewChemModel();
              string str;
              try
              {
                  string line;
                  while ((line = input.ReadLine()) != null)
                  {
                      Debug.WriteLine($"line: {line}");
                      // apparently, this is a SDF file, continue with
                      // reading mol files
                      str = line;
                      if (string.Equals(line, "M  END", StringComparison.Ordinal))
                          continue;
                      if (string.Equals(str, "$$$$", StringComparison.Ordinal))
                      {
                          m = ReadMolecule(chemFile.Builder.NewAtomContainer());
  
                          if (m != null)
                          {
                              setOfMolecules.Add(m);
  
                              chemModel.MoleculeSet = setOfMolecules;
                              chemSequence.Add(chemModel);
  
                              setOfMolecules = chemFile.Builder.NewAtomContainerSet();
                              chemModel = chemFile.Builder.NewChemModel();
  
                          }
                      }
                      else
                      {
                          // here the stuff between 'M  END' and '$$$$'
                          if (m != null)
                          {
                              // ok, the first lines should start with '>'
                              string fieldName = null;
                              if (str.StartsWith("> ", StringComparison.Ordinal))
                              {
                                  // ok, should extract the field name
                                  int index = str.IndexOf('<', 2);
                                  if (index != -1)
                                  {
                                      int index2 = str.Substring(index).IndexOf('>');
                                      if (index2 != -1)
                                      {
                                          fieldName = str.Substring(index + 1, index2 - 1);
                                      }
                                  }
                                  // end skip all other lines
                                  while ((line = input.ReadLine()) != null && line.StartsWithChar('>'))
                                  {
                                      Debug.WriteLine($"data header line: {line}");
                                  }
                              }
                              if (line == null)
                              {
                                  throw new CDKException("Expecting data line here, but found null!");
                              }
                              string data = line;
                              while ((line = input.ReadLine()) != null && line.Trim().Length > 0)
                              {
                                  if (string.Equals(line, "$$$$", StringComparison.Ordinal))
                                  {
                                      Trace.TraceError($"Expecting data line here, but found end of molecule: {line}");
                                      break;
                                  }
                                  Debug.WriteLine($"data line: {line}");
                                  data += line;
                                  // preserve newlines, unless the line is exactly 80 chars; in that case it
                                  // is assumed to continue on the next line. See MDL documentation.
                                  if (line.Length < 80) data += "\n";
                              }
                              if (fieldName != null)
                              {
                                  Trace.TraceInformation($"fieldName, data: {fieldName}, {data}");
                                  m.SetProperty(fieldName, data);
                              }
                          }
                      }
                  }
              }
              catch (CDKException)
              {
                  throw;
              }
              catch (Exception exception)
              {
                  if (!(exception is IOException || exception is ArgumentException))
                      throw;
                  string error = "Error while parsing SDF";
                  Trace.TraceError(error);
                  Debug.WriteLine(exception);
                  throw new CDKException(error, exception);
              }
              try
              {
                  input.Close();
              }
              catch (Exception exc)
              {
                  string error = "Error while closing file: " + exc.Message;
                  Trace.TraceError(error);
                  throw new CDKException(error, exc);
              }
  
              chemFile.Add(chemSequence);
              return chemFile;
          }
  
          /// <summary>
          ///  Read a Molecule from a file in MDL sd format
          /// </summary>
          /// <returns>The Molecule that was read from the MDL file.</returns>
          private IAtomContainer ReadMolecule(IAtomContainer molecule)
          {
              Debug.WriteLine("Reading new molecule");
              int linecount = 0;
              int atoms = 0;
              int bonds = 0;
              int atom1 = 0;
              int atom2 = 0;
              int order = 0;
              BondStereo stereo = BondStereo.None;
              int RGroupCounter = 1;
              int Rnumber = 0;
              double x = 0.0;
              double y = 0.0;
              double z = 0.0;
              double totalX = 0.0;
              double totalY = 0.0;
              double totalZ = 0.0;
              //int[][] conMat = Array.Empty<int>()[0];
              //string help;
              IAtom atom;
              string line = "";
  
              try
              {
                  var isotopeFactory = CDK.IsotopeFactory;
                  Trace.TraceInformation("Reading header");
                  line = input.ReadLine();
                  linecount++;
                  if (line == null)
                  {
                      return null;
                  }
                  Debug.WriteLine("Line " + linecount + ": " + line);
  
                  if (line.StartsWith("$$$$", StringComparison.Ordinal))
                  {
                      Debug.WriteLine("File is empty, returning empty molecule");
                      return molecule;
                  }
                  if (line.Length > 0)
                  {
                      molecule.Title = line;
                  }
                  line = input.ReadLine();
                  linecount++;
                  Debug.WriteLine("Line " + linecount + ": " + line);
                  line = input.ReadLine();
                  linecount++;
                  Debug.WriteLine("Line " + linecount + ": " + line);
                  if (line.Length > 0)
                  {
                      molecule.SetProperty(CDKPropertyName.Remark, line);
                  }
  
                  Trace.TraceInformation("Reading rest of file");
                  line = input.ReadLine();
                  linecount++;
                  Debug.WriteLine("Line " + linecount + ": " + line);
                  if (ReaderMode == ChemObjectReaderMode.Strict)
                  {
                      if (line.Contains("V2000") || line.Contains("v2000"))
                      {
                          throw new CDKException("This file must be read with the MDLV2000Reader.");
                      }
                      if (line.Contains("V3000") || line.Contains("v3000"))
                      {
                          throw new CDKException("This file must be read with the MDLV3000Reader.");
                      }
                  }
                  atoms = int.Parse(Strings.Substring(line, 0, 3).Trim(), NumberFormatInfo.InvariantInfo);
                  Debug.WriteLine($"Atomcount: {atoms}");
                  bonds = int.Parse(Strings.Substring(line, 3, 3).Trim(), NumberFormatInfo.InvariantInfo);
                  Debug.WriteLine($"Bondcount: {bonds}");
  
                  // read ATOM block
                  Trace.TraceInformation("Reading atom block");
                  for (int f = 0; f < atoms; f++)
                  {
                      line = input.ReadLine();
                      linecount++;
                      var trailingSpaceMatcher = TRAILING_SPACE.Match(line);
                      if (trailingSpaceMatcher.Success)
                      {
                          HandleError("Trailing space found", linecount, trailingSpaceMatcher.Index, trailingSpaceMatcher.Index + trailingSpaceMatcher.Length);
                          line = Strings.Substring(line, 0, trailingSpaceMatcher.Index);
                      }
                      x = double.Parse(Strings.Substring(line, 0, 10).Trim(), NumberFormatInfo.InvariantInfo);
                      y = double.Parse(Strings.Substring(line, 10, 10).Trim(), NumberFormatInfo.InvariantInfo);
                      z = double.Parse(Strings.Substring(line, 20, 10).Trim(), NumberFormatInfo.InvariantInfo);
                      // *all* values should be zero, not just the sum
                      totalX += Math.Abs(x);
                      totalY += Math.Abs(y);
                      totalZ += Math.Abs(z);
                      Debug.WriteLine("Coordinates: " + x + "; " + y + "; " + z);
                      string element = Strings.Substring(line, 31, 3).Trim();
                      if (line.Length < 34)
                      {
                          HandleError("Element atom type does not follow V2000 format type should of length three"
                                  + " and padded with space if required", linecount, 31, 34);
                      }
  
                      Debug.WriteLine($"Atom type: {element}");
                      if (isotopeFactory.IsElement(element))
                      {
                          atom = isotopeFactory.Configure(molecule.Builder.NewAtom(element));
                      }
                      else if (string.Equals("A", element, StringComparison.Ordinal))
                      {
                          atom = molecule.Builder.NewPseudoAtom(element);
                      }
                      else if (string.Equals("Q", element, StringComparison.Ordinal))
                      {
                          atom = molecule.Builder.NewPseudoAtom(element);
                      }
                      else if (string.Equals("*", element, StringComparison.Ordinal))
                      {
  
gt:                         atom = molecule.Builder.NewPseudoAtom(element);