// ===== BEGIN USFlexalign.cpp =====
/* command line argument parsing and document of US-align main program */

#include <iostream>
using namespace std;

void print_version()
{
    cout << 
"\n"
" ********************************************************************\n"
" * US-Flexalign (Version 20260908)                                  *\n"
" * Flexible Structure Alignment of Proteins and Nucleic Acids       *\n"
" * Reference: Y Zhu, S Yan, Y Zhang, C Zhang (2026)                 *\n"
" * Please email comments and suggestions to cx.zhang2@siat.ac.cn    *\n"
" ********************************************************************"
    << endl;
}

void print_extra_help()
{
    cout << "Additional options:\n"
            "      -v  Print the version of US-alignFlex\n"
            "\n"
            "    -mol  Type of molecule(s) to align.\n"
            "          auto: (default) align both protein and nucleic acids.\n"
            "          prot: only align proteins in a structure.\n"
            "          RNA : only align RNA and DNA in a structure.\n"
            "\n"
            "    -ter  Number of chains to align.\n"
            "          0: align all chains from all models (recommended for aligning\n"
            "             biological assemblies, i.e. biounits)\n"
            "          1: align all chains of the first model (recommended for aligning\n"
            "             asymmetric units)\n"
            "          2: (default) only align the first chain\n"
            "          3: only align the first chain, or the first segment of the\n"
            "             first chain as marked by the 'TER' string in PDB file\n"
            "\n"
            "      -m  Output rotation matrix for superposition, e.g., '-m matrix.txt'\n"
            "          prints the matrix to 'matrix.txt'; '-m -' prints to stdout.\n"
            "\n"
            "      -d  TM-score scaled by an assigned d0, e.g., '-d 3.5' reports MaxSub\n"
            "          score, where d0 is 3.5 Angstrom. -d does not change final alignment.\n"
            "\n"
            "      -u  TM-score normalized by an assigned length. It should be >= length\n"
            "          of protein to avoid TM-score >1. -u does not change final alignment.\n"
            "\n"
            "      -o  Output superposed structure1 to sup.* for PyMOL viewing.\n"
            "          $ USalign structure1.pdb structure2.pdb -o sup\n"
            "          $ pymol -d @sup.pml                # C-alpha trace aligned region\n"
            "          $ pymol -d @sup_all.pml            # C-alpha trace whole chain\n"
            "          $ pymol -d @sup_atm.pml            # full-atom aligned region\n"
            "          $ pymol -d @sup_all_atm.pml        # full-atom whole chain\n"
            "          $ pymol -d @sup_all_atm_lig.pml    # full-atom with all molecules\n"
            "\n"
            " -rasmol  Output superposed structure1 to sup.* for RasMol viewing.\n"
            "          $ USalign structure1.pdb structure2.pdb -rasmol sup\n"
            "          $ rasmol -script sup.spt             # C-alpha trace aligned region\n"
            "          $ rasmol -script sup_all.spt         # C-alpha trace whole chain\n"
            "          $ rasmol -script sup_atm.spt         # full-atom aligned region\n"
            "          $ rasmol -script sup_all_atm.spt     # full-atom whole chain\n"
            "          $ rasmol -script sup_all_atm_lig.spt # full-atom with all molecules\n"
            "\n"
            "-chimerax Output superposed structure1 to sup.* for ChimeraX viewing.\n"
            "          $ USalign structure1.pdb structure2.pdb -chimerax sup\n"
            "          $ chimerax --script sup.cxc             # C-alpha trace aligned region\n"
            "          $ chimerax --script sup_all.cxc         # C-alpha trace whole chain\n"
            "          $ chimerax --script sup_atm.cxc         # full-atom aligned region\n"
            "          $ chimerax --script sup_all_atm.cxc     # full-atom whole chain\n"
            "          $ chimerax --script sup_all_atm_lig.cxc # full-atom with all molecules\n"
            "\n"
            "      -a  TM-score normalized by the average length of two structures\n"
            "          T or F, (default F). -a does not change the final alignment.\n"
            "\n"
            "   -fast  Fast but slightly inaccurate alignment\n"
            "\n"
            "    -dir  Perform all-against-all alignment among the list of PDB\n"
            "          chains listed by 'chain_list' under 'chain_folder'.\n"
            "          $ USalign -dir chain_folder/ chain_list\n"
            "\n"
            //"-dirpair  Perform batch alignment for each pair of chains listed by\n"
            //"          'chain_pair_list' under 'chain_folder'. Each line consist of\n"
            //"          two chains, separated by tab or space.\n"
            //"          $ USalign -dirpair chain_folder/ chain_pair_list\n"
            //"\n"
            "   -dir1  Use chain2 to search a list of PDB chains listed by 'chain1_list'\n"
            "          under 'chain1_folder'.\n"
            "          $ USalign -dir1 chain1_folder/ chain1_list chain2\n"
            "\n"
            "   -dir2  Use chain1 to search a list of PDB chains listed by 'chain2_list'\n"
            "          under 'chain2_folder'\n"
            "          $ USalign chain1 -dir2 chain2_folder/ chain2_list\n"
            "\n"
            " -suffix  (Only when -dir1 and/or -dir2 are set, default is empty)\n"
            "          add file name suffix to files listed by chain1_list or chain2_list\n"
            "\n"
            "   -atom  4-character atom name used to represent a residue.\n"
            "          Default is \" C3'\" for RNA/DNA and \" CA \" for proteins\n"
            "          (note the spaces before and after CA).\n"
            "\n"
            "  -split  Whether to split PDB file into multiple chains\n"
            "           0: treat the whole structure as one single chain\n"
            "           1: treat each MODEL as a separate chain\n"
            "           2: (default) treat each chain as a separate chain\n"
            "\n"
            " -outfmt  Output format\n"
            "           0: (default) full output\n"
            "           1: fasta format compact output\n"
            "           2: tabular format very compact output\n"
            "          -1: full output, but without version or citation information\n"
            "\n"
            " -mirror  Whether to align the mirror image of input structure\n"
            "           0: (default) do not align mirrored structure\n"
            "           1: align mirror of Structure_1 to origin Structure_2,\n"
            "              which usually requires the '-het 1' option:\n"
            "              $ USalign 4glu.pdb 3p9w.pdb -mirror 1 -het 1\n"
            "\n"
            "    -het  Whether to align residues marked as 'HETATM' in addition to 'ATOM  '\n"
            "           0: (default) only align 'ATOM  ' residues\n"
            "           1: align both 'ATOM  ' and 'HETATM' residues\n"
            "           2: align both 'ATOM  ' and MSE residues\n"
            "\n"
            " -infmt1  Input format for structure_1\n"
            " -infmt2  Input format for structure_2\n"
            "          -1: (default) automatically detect PDB or PDBx/mmCIF format\n"
            "           0: PDB format\n"
            "           1: SPICKER format\n"
            //"           2: xyz format\n"
            "           3: PDBx/mmCIF format\n"
            "\n"
            "-chain1   Chains to parse in structure_1\n"
            "-chain2   Chains to parse in structure_2. Use _ for a chain without chain ID.\n"
            "          Multiple chains can be separated by commas, e.g.,\n"
            "          USalign -chain1 C,D,E,F 5jdo.pdb -chain2 A,B,C,D 3wtg.pdb -ter 0\n"
            "\n"
            "-model1   Models to parse in structure_1\n"
            "-model2   Models to parse in structure_2.\n"
            "          Multiple models can be separated by commas, e.g.,\n"
            "          USalign -model1 1,2 1a03.pdb -model2 3,4 1a0n.pdb -ter 0\n"
            "\n"
         << endl;
}

void print_help(bool h_opt = false)
{
    print_version();
    cout << "\n"
            "Usage: USalignFlex PDB1.pdb PDB2.pdb [Options]\n"
            "\n"
            "Options:\n"
            "  -hinge  Maximum number of hinge allowed in flexible alignment.\n"
            "          default: 9\n"
            "\n"
            "    -afp  Enable AFP-enhancement mechanism.\n"
            "\n"
            " -TMpass  Early stopping threshold for AFP-enhancement mechanism.\n"
            "          default: 0.85\n"
            "\n"
            "      -h  Print the full help message, including additional options\n"
            "\n"
            "Example usages ('gunzip' program is needed to read .gz compressed files):\n"
            "    USalignFlex 1oid.pdb 2wde.pdb\n"
         << endl;

    if (h_opt)
        print_extra_help();

    exit(EXIT_SUCCESS);
}

// Original: #include "se.h"
// ===== BEGIN se.h =====
// Original: #include "TMalign.h"
// ===== BEGIN TMalign.h =====
/* Functions for the core TMalign algorithm, including the entry function
 * TMalign_main */
#ifndef TMalign_h
#define TMalign_h 1

// Original: #include "param_set.h"
// ===== BEGIN param_set.h =====
/* These functions implement d0 normalization. The d0 for final TM-score
 * output is implemented by parameter_set4final. For both RNA alignment
 * and protein alignment, using d0 set by parameter_set4search yields
 * slightly better results during initial alignment-superposition iteration.
 */
#include <math.h>
// Original: #include "basic_fun.h"
// ===== BEGIN basic_fun.h =====
/* File parsing and basic geometry operations */
#ifndef TMalign_basic_fun_h
#define TMalign_basic_fun_h 1

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>
//#include <malloc.h>

#include <sstream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <string>
#include <iomanip>
#include <map>

// Original: #include "pstream.h" // For reading gzip and bz2 compressed files
// ===== BEGIN pstream.h =====
// PStreams - POSIX Process I/O for C++

//        Copyright (C) 2001 - 2017 Jonathan Wakely
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)
//

/**
 * @file pstream.h
 * @brief Declares all PStreams classes.
 * @author Jonathan Wakely
 *
 * Defines classes redi::ipstream, redi::opstream, redi::pstream
 * and redi::rpstream.
 */

/* do not compile on windows, which does not have cygwin */
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__CYGWIN__)
#define NO_PSTREAM
#else

#ifndef REDI_PSTREAM_H_SEEN
#define REDI_PSTREAM_H_SEEN

#include <ios>
#include <streambuf>
#include <istream>
#include <ostream>
#include <string>
#include <vector>
#include <algorithm>    // for min()
#include <cerrno>       // for errno
#include <cstddef>      // for size_t, NULL
#include <cstdlib>      // for exit()
#include <sys/types.h>  // for pid_t
#include <sys/wait.h>   // for waitpid()
#include <sys/ioctl.h>  // for ioctl() and FIONREAD
#if defined(__sun)
# include <sys/filio.h> // for FIONREAD on Solaris 2.5
#endif
#include <unistd.h>     // for pipe() fork() exec() and filedes functions
#include <signal.h>     // for kill()
#include <fcntl.h>      // for fcntl()
#if REDI_EVISCERATE_PSTREAMS
# include <stdio.h>     // for FILE, fdopen()
#endif


/// The library version.
#define PSTREAMS_VERSION 0x0101   // 1.0.1

/**
 *  @namespace redi
 *  @brief  All PStreams classes are declared in namespace redi.
 *
 *  Like the standard iostreams, PStreams is a set of class templates,
 *  taking a character type and traits type. As with the standard streams
 *  they are most likely to be used with @c char and the default
 *  traits type, so typedefs for this most common case are provided.
 *
 *  The @c pstream_common class template is not intended to be used directly,
 *  it is used internally to provide the common functionality for the
 *  other stream classes.
 */
namespace redi
{
  /// Common base class providing constants and typenames.
  struct pstreams
  {
    /// Type used to specify how to connect to the process.
    typedef std::ios_base::openmode           pmode;

    /// Type used to hold the arguments for a command.
    typedef std::vector<std::string>          argv_type;

    /// Type used for file descriptors.
    typedef int                               fd_type;

    static const pmode pstdin  = std::ios_base::out; ///< Write to stdin
    static const pmode pstdout = std::ios_base::in;  ///< Read from stdout
    static const pmode pstderr = std::ios_base::app; ///< Read from stderr

    /// Create a new process group for the child process.
    static const pmode newpg   = std::ios_base::trunc;

  protected:
    enum { bufsz = 32 };  ///< Size of pstreambuf buffers.
    enum { pbsz  = 2 };   ///< Number of putback characters kept.
  };

  /// Class template for stream buffer.
  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class basic_pstreambuf
    : public std::basic_streambuf<CharT, Traits>
    , public pstreams
    {
    public:
      // Type definitions for dependent types
      typedef CharT                             char_type;
      typedef Traits                            traits_type;
      typedef typename traits_type::int_type    int_type;
      typedef typename traits_type::off_type    off_type;
      typedef typename traits_type::pos_type    pos_type;
      /** @deprecated use pstreams::fd_type instead. */
      typedef fd_type                           fd_t;

      /// Default constructor.
      basic_pstreambuf();

      /// Constructor that initialises the buffer with @a cmd.
      basic_pstreambuf(const std::string& cmd, pmode mode);

      /// Constructor that initialises the buffer with @a file and @a argv.
      basic_pstreambuf( const std::string& file,
                        const argv_type& argv,
                        pmode mode );

      /// Destructor.
      ~basic_pstreambuf();

      /// Initialise the stream buffer with @a cmd.
      basic_pstreambuf*
      open(const std::string& cmd, pmode mode);

      /// Initialise the stream buffer with @a file and @a argv.
      basic_pstreambuf*
      open(const std::string& file, const argv_type& argv, pmode mode);

      /// Close the stream buffer and wait for the process to exit.
      basic_pstreambuf*
      close();

      /// Send a signal to the process.
      basic_pstreambuf*
      kill(int signal = SIGTERM);

      /// Send a signal to the process' process group.
      basic_pstreambuf*
      killpg(int signal = SIGTERM);

      /// Close the pipe connected to the process' stdin.
      void
      peof();

      /// Change active input source.
      bool
      read_err(bool readerr = true);

      /// Report whether the stream buffer has been initialised.
      bool
      is_open() const;

      /// Report whether the process has exited.
      bool
      exited();

#if REDI_EVISCERATE_PSTREAMS
      /// Obtain FILE pointers for each of the process' standard streams.
      std::size_t
      fopen(FILE*& in, FILE*& out, FILE*& err);
#endif

      /// Return the exit status of the process.
      int
      status() const;

      /// Return the error number (errno) for the most recent failed operation.
      int
      error() const;

    protected:
      /// Transfer characters to the pipe when character buffer overflows.
      int_type
      overflow(int_type c);

      /// Transfer characters from the pipe when the character buffer is empty.
      int_type
      underflow();

      /// Make a character available to be returned by the next extraction.
      int_type
      pbackfail(int_type c = traits_type::eof());

      /// Write any buffered characters to the stream.
      int
      sync();

      /// Insert multiple characters into the pipe.
      std::streamsize
      xsputn(const char_type* s, std::streamsize n);

      /// Insert a sequence of characters into the pipe.
      std::streamsize
      write(const char_type* s, std::streamsize n);

      /// Extract a sequence of characters from the pipe.
      std::streamsize
      read(char_type* s, std::streamsize n);

      /// Report how many characters can be read from active input without blocking.
      std::streamsize
      showmanyc();

    protected:
      /// Enumerated type to indicate whether stdout or stderr is to be read.
      enum buf_read_src { rsrc_out = 0, rsrc_err = 1 };

      /// Initialise pipes and fork process.
      pid_t
      fork(pmode mode);

      /// Wait for the child process to exit.
      int
      wait(bool nohang = false);

      /// Return the file descriptor for the output pipe.
      fd_type&
      wpipe();

      /// Return the file descriptor for the active input pipe.
      fd_type&
      rpipe();

      /// Return the file descriptor for the specified input pipe.
      fd_type&
      rpipe(buf_read_src which);

      void
      create_buffers(pmode mode);

      void
      destroy_buffers(pmode mode);

      /// Writes buffered characters to the process' stdin pipe.
      bool
      empty_buffer();

      bool
      fill_buffer(bool non_blocking = false);

      /// Return the active input buffer.
      char_type*
      rbuffer();

      buf_read_src
      switch_read_buffer(buf_read_src);

    private:
      basic_pstreambuf(const basic_pstreambuf&);
      basic_pstreambuf& operator=(const basic_pstreambuf&);

      void
      init_rbuffers();

      pid_t         ppid_;        // pid of process
      fd_type       wpipe_;       // pipe used to write to process' stdin
      fd_type       rpipe_[2];    // two pipes to read from, stdout and stderr
      char_type*    wbuffer_;
      char_type*    rbuffer_[2];
      char_type*    rbufstate_[3];
      /// Index into rpipe_[] to indicate active source for read operations.
      buf_read_src  rsrc_;
      int           status_;      // hold exit status of child process
      int           error_;       // hold errno if fork() or exec() fails
    };

  /// Class template for common base class.
  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class pstream_common
    : virtual public std::basic_ios<CharT, Traits>
    , virtual public pstreams
    {
    protected:
      typedef basic_pstreambuf<CharT, Traits>       streambuf_type;

      typedef pstreams::pmode                       pmode;
      typedef pstreams::argv_type                   argv_type;

      /// Default constructor.
      pstream_common();

      /// Constructor that initialises the stream by starting a process.
      pstream_common(const std::string& cmd, pmode mode);

      /// Constructor that initialises the stream by starting a process.
      pstream_common(const std::string& file, const argv_type& argv, pmode mode);

      /// Pure virtual destructor.
      virtual
      ~pstream_common() = 0;

      /// Start a process.
      void
      do_open(const std::string& cmd, pmode mode);

      /// Start a process.
      void
      do_open(const std::string& file, const argv_type& argv, pmode mode);

    public:
      /// Close the pipe.
      void
      close();

      /// Report whether the stream's buffer has been initialised.
      bool
      is_open() const;

      /// Return the command used to initialise the stream.
      const std::string&
      command() const;

      /// Return a pointer to the stream buffer.
      streambuf_type*
      rdbuf() const;

#if REDI_EVISCERATE_PSTREAMS
      /// Obtain FILE pointers for each of the process' standard streams.
      std::size_t
      fopen(FILE*& in, FILE*& out, FILE*& err);
#endif

    protected:
      std::string       command_; ///< The command used to start the process.
      streambuf_type    buf_;     ///< The stream buffer.
    };


  /**
   * @class basic_ipstream
   * @brief Class template for Input PStreams.
   *
   * Reading from an ipstream reads the command's standard output and/or
   * standard error (depending on how the ipstream is opened)
   * and the command's standard input is the same as that of the process
   * that created the object, unless altered by the command itself.
   */

  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class basic_ipstream
    : public std::basic_istream<CharT, Traits>
    , public pstream_common<CharT, Traits>
    , virtual public pstreams
    {
      typedef std::basic_istream<CharT, Traits>     istream_type;
      typedef pstream_common<CharT, Traits>         pbase_type;

      using pbase_type::buf_;  // declare name in this scope

      // Ensure a basic_ipstream will read from at least one pipe
      pmode readable(pmode mode)
      {
        if (!(mode & (pstdout|pstderr)))
          mode |= pstdout;
        return mode;
      }

    public:
      /// Type used to specify how to connect to the process.
      typedef typename pbase_type::pmode            pmode;

      /// Type used to hold the arguments for a command.
      typedef typename pbase_type::argv_type        argv_type;

      /// Default constructor, creates an uninitialised stream.
      basic_ipstream()
      : istream_type(NULL), pbase_type()
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      explicit
      basic_ipstream(const std::string& cmd, pmode mode = pstdout)
      : istream_type(NULL), pbase_type(cmd, readable(mode))
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      basic_ipstream( const std::string& file,
                      const argv_type& argv,
                      pmode mode = pstdout )
      : istream_type(NULL), pbase_type(file, argv, readable(mode))
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling
       * @c do_open(argv[0],argv,mode|pstdout)
       *
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      explicit
      basic_ipstream(const argv_type& argv, pmode mode = pstdout)
      : istream_type(NULL), pbase_type(argv.at(0), argv, readable(mode))
      { }

#if __cplusplus >= 201103L
      template<typename T>
        explicit
        basic_ipstream(std::initializer_list<T> args, pmode mode = pstdout)
        : basic_ipstream(argv_type(args.begin(), args.end()), mode)
        { }
#endif

      /**
       * @brief Destructor.
       *
       * Closes the stream and waits for the child to exit.
       */
      ~basic_ipstream()
      { }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a cmd , @a mode|pstdout ).
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      void
      open(const std::string& cmd, pmode mode = pstdout)
      {
        this->do_open(cmd, readable(mode));
      }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a file , @a argv , @a mode|pstdout ).
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      void
      open( const std::string& file,
            const argv_type& argv,
            pmode mode = pstdout )
      {
        this->do_open(file, argv, readable(mode));
      }

      /**
       * @brief Set streambuf to read from process' @c stdout.
       * @return  @c *this
       */
      basic_ipstream&
      out()
      {
        this->buf_.read_err(false);
        return *this;
      }

      /**
       * @brief Set streambuf to read from process' @c stderr.
       * @return  @c *this
       */
      basic_ipstream&
      err()
      {
        this->buf_.read_err(true);
        return *this;
      }
    };


  /**
   * @class basic_opstream
   * @brief Class template for Output PStreams.
   *
   * Writing to an open opstream writes to the standard input of the command;
   * the command's standard output is the same as that of the process that
   * created the pstream object, unless altered by the command itself.
   */

  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class basic_opstream
    : public std::basic_ostream<CharT, Traits>
    , public pstream_common<CharT, Traits>
    , virtual public pstreams
    {
      typedef std::basic_ostream<CharT, Traits>     ostream_type;
      typedef pstream_common<CharT, Traits>         pbase_type;

      using pbase_type::buf_;  // declare name in this scope

    public:
      /// Type used to specify how to connect to the process.
      typedef typename pbase_type::pmode            pmode;

      /// Type used to hold the arguments for a command.
      typedef typename pbase_type::argv_type        argv_type;

      /// Default constructor, creates an uninitialised stream.
      basic_opstream()
      : ostream_type(NULL), pbase_type()
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      explicit
      basic_opstream(const std::string& cmd, pmode mode = pstdin)
      : ostream_type(NULL), pbase_type(cmd, mode|pstdin)
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      basic_opstream( const std::string& file,
                      const argv_type& argv,
                      pmode mode = pstdin )
      : ostream_type(NULL), pbase_type(file, argv, mode|pstdin)
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling
       * @c do_open(argv[0],argv,mode|pstdin)
       *
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      explicit
      basic_opstream(const argv_type& argv, pmode mode = pstdin)
      : ostream_type(NULL), pbase_type(argv.at(0), argv, mode|pstdin)
      { }

#if __cplusplus >= 201103L
      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * @param args  a list of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      template<typename T>
        explicit
        basic_opstream(std::initializer_list<T> args, pmode mode = pstdin)
        : basic_opstream(argv_type(args.begin(), args.end()), mode)
        { }
#endif

      /**
       * @brief Destructor
       *
       * Closes the stream and waits for the child to exit.
       */
      ~basic_opstream() { }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a cmd , @a mode|pstdin ).
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      void
      open(const std::string& cmd, pmode mode = pstdin)
      {
        this->do_open(cmd, mode|pstdin);
      }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a file , @a argv , @a mode|pstdin ).
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      void
      open( const std::string& file,
            const argv_type& argv,
            pmode mode = pstdin)
      {
        this->do_open(file, argv, mode|pstdin);
      }
    };


  /**
   * @class basic_pstream
   * @brief Class template for Bidirectional PStreams.
   *
   * Writing to a pstream opened with @c pmode @c pstdin writes to the
   * standard input of the command.
   * Reading from a pstream opened with @c pmode @c pstdout and/or @c pstderr
   * reads the command's standard output and/or standard error.
   * Any of the process' @c stdin, @c stdout or @c stderr that is not
   * connected to the pstream (as specified by the @c pmode)
   * will be the same as the process that created the pstream object,
   * unless altered by the command itself.
   */
  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class basic_pstream
    : public std::basic_iostream<CharT, Traits>
    , public pstream_common<CharT, Traits>
    , virtual public pstreams
    {
      typedef std::basic_iostream<CharT, Traits>    iostream_type;
      typedef pstream_common<CharT, Traits>         pbase_type;

      using pbase_type::buf_;  // declare name in this scope

    public:
      /// Type used to specify how to connect to the process.
      typedef typename pbase_type::pmode            pmode;

      /// Type used to hold the arguments for a command.
      typedef typename pbase_type::argv_type        argv_type;

      /// Default constructor, creates an uninitialised stream.
      basic_pstream()
      : iostream_type(NULL), pbase_type()
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      explicit
      basic_pstream(const std::string& cmd, pmode mode = pstdout|pstdin)
      : iostream_type(NULL), pbase_type(cmd, mode)
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      basic_pstream( const std::string& file,
                     const argv_type& argv,
                     pmode mode = pstdout|pstdin )
      : iostream_type(NULL), pbase_type(file, argv, mode)
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling
       * @c do_open(argv[0],argv,mode)
       *
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      explicit
      basic_pstream(const argv_type& argv, pmode mode = pstdout|pstdin)
      : iostream_type(NULL), pbase_type(argv.at(0), argv, mode)
      { }

#if __cplusplus >= 201103L
      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * @param l     a list of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      template<typename T>
        explicit
        basic_pstream(std::initializer_list<T> l, pmode mode = pstdout|pstdin)
        : basic_pstream(argv_type(l.begin(), l.end()), mode)
        { }
#endif

      /**
       * @brief Destructor
       *
       * Closes the stream and waits for the child to exit.
       */
      ~basic_pstream() { }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a cnd , @a mode ).
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      void
      open(const std::string& cmd, pmode mode = pstdout|pstdin)
      {
        this->do_open(cmd, mode);
      }

      /**
       * @brief Start a process.
       *
       * Calls do_open( @a file , @a argv , @a mode ).
       *
       * @param file  a string containing the pathname of a program to execute.
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      void
      open( const std::string& file,
            const argv_type& argv,
            pmode mode = pstdout|pstdin )
      {
        this->do_open(file, argv, mode);
      }

      /**
       * @brief Set streambuf to read from process' @c stdout.
       * @return  @c *this
       */
      basic_pstream&
      out()
      {
        this->buf_.read_err(false);
        return *this;
      }

      /**
       * @brief Set streambuf to read from process' @c stderr.
       * @return  @c *this
       */
      basic_pstream&
      err()
      {
        this->buf_.read_err(true);
        return *this;
      }
    };


  /**
   * @class basic_rpstream
   * @brief Class template for Restricted PStreams.
   *
   * Writing to an rpstream opened with @c pmode @c pstdin writes to the
   * standard input of the command.
   * It is not possible to read directly from an rpstream object, to use
   * an rpstream as in istream you must call either basic_rpstream::out()
   * or basic_rpstream::err(). This is to prevent accidental reads from
   * the wrong input source. If the rpstream was not opened with @c pmode
   * @c pstderr then the class cannot read the process' @c stderr, and
   * basic_rpstream::err() will return an istream that reads from the
   * process' @c stdout, and vice versa.
   * Reading from an rpstream opened with @c pmode @c pstdout and/or
   * @c pstderr reads the command's standard output and/or standard error.
   * Any of the process' @c stdin, @c stdout or @c stderr that is not
   * connected to the pstream (as specified by the @c pmode)
   * will be the same as the process that created the pstream object,
   * unless altered by the command itself.
   */

  template <typename CharT, typename Traits = std::char_traits<CharT> >
    class basic_rpstream
    : public std::basic_ostream<CharT, Traits>
    , private std::basic_istream<CharT, Traits>
    , private pstream_common<CharT, Traits>
    , virtual public pstreams
    {
      typedef std::basic_ostream<CharT, Traits>     ostream_type;
      typedef std::basic_istream<CharT, Traits>     istream_type;
      typedef pstream_common<CharT, Traits>         pbase_type;

      using pbase_type::buf_;  // declare name in this scope

    public:
      /// Type used to specify how to connect to the process.
      typedef typename pbase_type::pmode            pmode;

      /// Type used to hold the arguments for a command.
      typedef typename pbase_type::argv_type        argv_type;

      /// Default constructor, creates an uninitialised stream.
      basic_rpstream()
      : ostream_type(NULL), istream_type(NULL), pbase_type()
      { }

      /**
       * @brief  Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      explicit
      basic_rpstream(const std::string& cmd, pmode mode = pstdout|pstdin)
      : ostream_type(NULL) , istream_type(NULL) , pbase_type(cmd, mode)
      { }

      /**
       * @brief  Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling do_open() with the supplied
       * arguments.
       *
       * @param file a string containing the pathname of a program to execute.
       * @param argv a vector of argument strings passed to the new program.
       * @param mode the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      basic_rpstream( const std::string& file,
                      const argv_type& argv,
                      pmode mode = pstdout|pstdin )
      : ostream_type(NULL), istream_type(NULL), pbase_type(file, argv, mode)
      { }

      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * Initialises the stream buffer by calling
       * @c do_open(argv[0],argv,mode)
       *
       * @param argv  a vector of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      explicit
      basic_rpstream(const argv_type& argv, pmode mode = pstdout|pstdin)
      : ostream_type(NULL), istream_type(NULL),
        pbase_type(argv.at(0), argv, mode)
      { }

#if __cplusplus >= 201103L
      /**
       * @brief Constructor that initialises the stream by starting a process.
       *
       * @param l     a list of argument strings passed to the new program.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      template<typename T>
        explicit
        basic_rpstream(std::initializer_list<T> l, pmode mode = pstdout|pstdin)
        : basic_rpstream(argv_type(l.begin(), l.end()), mode)
        { }
#endif

      /// Destructor
      ~basic_rpstream() { }

      /**
       * @brief  Start a process.
       *
       * Calls do_open( @a cmd , @a mode ).
       *
       * @param cmd   a string containing a shell command.
       * @param mode  the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, pmode)
       */
      void
      open(const std::string& cmd, pmode mode = pstdout|pstdin)
      {
        this->do_open(cmd, mode);
      }

      /**
       * @brief  Start a process.
       *
       * Calls do_open( @a file , @a argv , @a mode ).
       *
       * @param file a string containing the pathname of a program to execute.
       * @param argv a vector of argument strings passed to the new program.
       * @param mode the I/O mode to use when opening the pipe.
       * @see   do_open(const std::string&, const argv_type&, pmode)
       */
      void
      open( const std::string& file,
            const argv_type& argv,
            pmode mode = pstdout|pstdin )
      {
        this->do_open(file, argv, mode);
      }

      /**
       * @brief  Obtain a reference to the istream that reads
       *         the process' @c stdout.
       * @return @c *this
       */
      istream_type&
      out()
      {
        this->buf_.read_err(false);
        return *this;
      }

      /**
       * @brief  Obtain a reference to the istream that reads
       *         the process' @c stderr.
       * @return @c *this
       */
      istream_type&
      err()
      {
        this->buf_.read_err(true);
        return *this;
      }
    };


  /// Type definition for common template specialisation.
  typedef basic_pstreambuf<char> pstreambuf;
  /// Type definition for common template specialisation.
  typedef basic_ipstream<char> ipstream;
  /// Type definition for common template specialisation.
  typedef basic_opstream<char> opstream;
  /// Type definition for common template specialisation.
  typedef basic_pstream<char> pstream;
  /// Type definition for common template specialisation.
  typedef basic_rpstream<char> rpstream;


  /**
   * When inserted into an output pstream the manipulator calls
   * basic_pstreambuf<C,T>::peof() to close the output pipe,
   * causing the child process to receive the end-of-file indicator
   * on subsequent reads from its @c stdin stream.
   *
   * @brief   Manipulator to close the pipe connected to the process' stdin.
   * @param   s  An output PStream class.
   * @return  The stream object the manipulator was invoked on.
   * @see     basic_pstreambuf<C,T>::peof()
   * @relates basic_opstream basic_pstream basic_rpstream
   */
  template <typename C, typename T>
    inline std::basic_ostream<C,T>&
    peof(std::basic_ostream<C,T>& s)
    {
      typedef basic_pstreambuf<C,T> pstreambuf_type;
      if (pstreambuf_type* p = dynamic_cast<pstreambuf_type*>(s.rdbuf()))
        p->peof();
      return s;
    }


  /*
   * member definitions for pstreambuf
   */


  /**
   * @class basic_pstreambuf
   * Provides underlying streambuf functionality for the PStreams classes.
   */

  /** Creates an uninitialised stream buffer. */
  template <typename C, typename T>
    inline
    basic_pstreambuf<C,T>::basic_pstreambuf()
    : ppid_(-1)   // initialise to -1 to indicate no process run yet.
    , wpipe_(-1)
    , wbuffer_(NULL)
    , rsrc_(rsrc_out)
    , status_(-1)
    , error_(0)
    {
      init_rbuffers();
    }

  /**
   * Initialises the stream buffer by calling open() with the supplied
   * arguments.
   *
   * @param cmd   a string containing a shell command.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see   open()
   */
  template <typename C, typename T>
    inline
    basic_pstreambuf<C,T>::basic_pstreambuf(const std::string& cmd, pmode mode)
    : ppid_(-1)   // initialise to -1 to indicate no process run yet.
    , wpipe_(-1)
    , wbuffer_(NULL)
    , rsrc_(rsrc_out)
    , status_(-1)
    , error_(0)
    {
      init_rbuffers();
      open(cmd, mode);
    }

  /**
   * Initialises the stream buffer by calling open() with the supplied
   * arguments.
   *
   * @param file  a string containing the name of a program to execute.
   * @param argv  a vector of argument strings passsed to the new program.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see   open()
   */
  template <typename C, typename T>
    inline
    basic_pstreambuf<C,T>::basic_pstreambuf( const std::string& file,
                                             const argv_type& argv,
                                             pmode mode )
    : ppid_(-1)   // initialise to -1 to indicate no process run yet.
    , wpipe_(-1)
    , wbuffer_(NULL)
    , rsrc_(rsrc_out)
    , status_(-1)
    , error_(0)
    {
      init_rbuffers();
      open(file, argv, mode);
    }

  /**
   * Closes the stream by calling close().
   * @see close()
   */
  template <typename C, typename T>
    inline
    basic_pstreambuf<C,T>::~basic_pstreambuf()
    {
      close();
    }

  /**
   * Starts a new process by passing @a command to the shell (/bin/sh)
   * and opens pipes to the process with the specified @a mode.
   *
   * If @a mode contains @c pstdout the initial read source will be
   * the child process' stdout, otherwise if @a mode  contains @c pstderr
   * the initial read source will be the child's stderr.
   *
   * Will duplicate the actions of  the  shell  in searching for an
   * executable file if the specified file name does not contain a slash (/)
   * character.
   *
   * @warning
   * There is no way to tell whether the shell command succeeded, this
   * function will always succeed unless resource limits (such as
   * memory usage, or number of processes or open files) are exceeded.
   * This means is_open() will return true even if @a command cannot
   * be executed.
   * Use pstreambuf::open(const std::string&, const argv_type&, pmode)
   * if you need to know whether the command failed to execute.
   *
   * @param   command  a string containing a shell command.
   * @param   mode     a bitwise OR of one or more of @c out, @c in, @c err.
   * @return  NULL if the shell could not be started or the
   *          pipes could not be opened, @c this otherwise.
   * @see     <b>execl</b>(3)
   */
  template <typename C, typename T>
    basic_pstreambuf<C,T>*
    basic_pstreambuf<C,T>::open(const std::string& command, pmode mode)
    {
      const char * shell_path = "/bin/sh";
#if 0
      const std::string argv[] = { "sh", "-c", command };
      return this->open(shell_path, argv_type(argv, argv+3), mode);
#else
      basic_pstreambuf<C,T>* ret = NULL;

      if (!is_open())
      {
        switch(fork(mode))
        {
        case 0 :
          // this is the new process, exec command
          ::execl(shell_path, "sh", "-c", command.c_str(), (char*)NULL);

          // can only reach this point if exec() failed

          // parent can get exit code from waitpid()
          ::_exit(errno);
          // using std::exit() would make static dtors run twice

        case -1 :
          // couldn't fork, error already handled in pstreambuf::fork()
          break;

        default :
          // this is the parent process
          // activate buffers
          create_buffers(mode);
          ret = this;
        }
      }
      return ret;
#endif
    }

  /**
   * @brief  Helper function to close a file descriptor.
   *
   * Inspects @a fd and calls <b>close</b>(3) if it has a non-negative value.
   *
   * @param   fd  a file descriptor.
   * @relates basic_pstreambuf
   */
  inline void
  close_fd(pstreams::fd_type& fd)
  {
    if (fd >= 0 && ::close(fd) == 0)
      fd = -1;
  }

  /**
   * @brief  Helper function to close an array of file descriptors.
   *
   * Calls @c close_fd() on each member of the array.
   * The length of the array is determined automatically by
   * template argument deduction to avoid errors.
   *
   * @param   fds  an array of file descriptors.
   * @relates basic_pstreambuf
   */
  template <int N>
    inline void
    close_fd_array(pstreams::fd_type (&fds)[N])
    {
      for (std::size_t i = 0; i < N; ++i)
        close_fd(fds[i]);
    }

  /**
   * Starts a new process by executing @a file with the arguments in
   * @a argv and opens pipes to the process with the specified @a mode.
   *
   * By convention @c argv[0] should be the file name of the file being
   * executed.
   *
   * If @a mode contains @c pstdout the initial read source will be
   * the child process' stdout, otherwise if @a mode  contains @c pstderr
   * the initial read source will be the child's stderr.
   *
   * Will duplicate the actions of  the  shell  in searching for an
   * executable file if the specified file name does not contain a slash (/)
   * character.
   *
   * Iff @a file is successfully executed then is_open() will return true.
   * Otherwise, pstreambuf::error() can be used to obtain the value of
   * @c errno that was set by <b>execvp</b>(3) in the child process.
   *
   * The exit status of the new process will be returned by
   * pstreambuf::status() after pstreambuf::exited() returns true.
   *
   * @param   file  a string containing the pathname of a program to execute.
   * @param   argv  a vector of argument strings passed to the new program.
   * @param   mode  a bitwise OR of one or more of @c out, @c in and @c err.
   * @return  NULL if a pipe could not be opened or if the program could
   *          not be executed, @c this otherwise.
   * @see     <b>execvp</b>(3)
   */
  template <typename C, typename T>
    basic_pstreambuf<C,T>*
    basic_pstreambuf<C,T>::open( const std::string& file,
                                 const argv_type& argv,
                                 pmode mode )
    {
      basic_pstreambuf<C,T>* ret = NULL;

      if (!is_open())
      {
        // constants for read/write ends of pipe
        enum { RD, WR };

        // open another pipe and set close-on-exec
        fd_type ck_exec[] = { -1, -1 };
        if (-1 == ::pipe(ck_exec)
            || -1 == ::fcntl(ck_exec[RD], F_SETFD, FD_CLOEXEC)
            || -1 == ::fcntl(ck_exec[WR], F_SETFD, FD_CLOEXEC))
        {
          error_ = errno;
          close_fd_array(ck_exec);
        }
        else
        {
          switch(fork(mode))
          {
          case 0 :
            // this is the new process, exec command
            {
              char** arg_v = new char*[argv.size()+1];
              for (std::size_t i = 0; i < argv.size(); ++i)
              {
                const std::string& src = argv[i];
                char*& dest = arg_v[i];
                dest = new char[src.size()+1];
                dest[ src.copy(dest, src.size()) ] = '\0';
              }
              arg_v[argv.size()] = NULL;

              ::execvp(file.c_str(), arg_v);

              // can only reach this point if exec() failed

              // parent can get error code from ck_exec pipe
              error_ = errno;

              while (::write(ck_exec[WR], &error_, sizeof(error_)) == -1
                  && errno == EINTR)
              { }

              ::close(ck_exec[WR]);
              ::close(ck_exec[RD]);

              ::_exit(error_);
              // using std::exit() would make static dtors run twice
            }

          case -1 :
            // couldn't fork, error already handled in pstreambuf::fork()
            close_fd_array(ck_exec);
            break;

          default :
            // this is the parent process

            // check child called exec() successfully
            ::close(ck_exec[WR]);
            switch (::read(ck_exec[RD], &error_, sizeof(error_)))
            {
            case 0:
              // activate buffers
              create_buffers(mode);
              ret = this;
              break;
            case -1:
              error_ = errno;
              break;
            default:
              // error_ contains error code from child
              // call wait() to clean up and set ppid_ to 0
              this->wait();
              break;
            }
            ::close(ck_exec[RD]);
          }
        }
      }
      return ret;
    }

  /**
   * Creates pipes as specified by @a mode and calls @c fork() to create
   * a new process. If the fork is successful the parent process stores
   * the child's PID and the opened pipes and the child process replaces
   * its standard streams with the opened pipes.
   *
   * If an error occurs the error code will be set to one of the possible
   * errors for @c pipe() or @c fork().
   * See your system's documentation for these error codes.
   *
   * @param   mode  an OR of pmodes specifying which of the child's
   *                standard streams to connect to.
   * @return  On success the PID of the child is returned in the parent's
   *          context and zero is returned in the child's context.
   *          On error -1 is returned and the error code is set appropriately.
   */
  template <typename C, typename T>
    pid_t
    basic_pstreambuf<C,T>::fork(pmode mode)
    {
      pid_t pid = -1;

      // Three pairs of file descriptors, for pipes connected to the
      // process' stdin, stdout and stderr
      // (stored in a single array so close_fd_array() can close all at once)
      fd_type fd[] = { -1, -1, -1, -1, -1, -1 };
      fd_type* const pin = fd;
      fd_type* const pout = fd+2;
      fd_type* const perr = fd+4;

      // constants for read/write ends of pipe
      enum { RD, WR };

      // N.B.
      // For the pstreambuf pin is an output stream and
      // pout and perr are input streams.

      if (!error_ && mode&pstdin && ::pipe(pin))
        error_ = errno;

      if (!error_ && mode&pstdout && ::pipe(pout))
        error_ = errno;

      if (!error_ && mode&pstderr && ::pipe(perr))
        error_ = errno;

      if (!error_)
      {
        pid = ::fork();
        switch (pid)
        {
          case 0 :
          {
            // this is the new process

            // for each open pipe close one end and redirect the
            // respective standard stream to the other end

            if (*pin >= 0)
            {
              ::close(pin[WR]);
              ::dup2(pin[RD], STDIN_FILENO);
              ::close(pin[RD]);
            }
            if (*pout >= 0)
            {
              ::close(pout[RD]);
              ::dup2(pout[WR], STDOUT_FILENO);
              ::close(pout[WR]);
            }
            if (*perr >= 0)
            {
              ::close(perr[RD]);
              ::dup2(perr[WR], STDERR_FILENO);
              ::close(perr[WR]);
            }

#ifdef _POSIX_JOB_CONTROL
            if (mode&newpg)
              ::setpgid(0, 0); // Change to a new process group
#endif

            break;
          }
          case -1 :
          {
            // couldn't fork for some reason
            error_ = errno;
            // close any open pipes
            close_fd_array(fd);
            break;
          }
          default :
          {
            // this is the parent process, store process' pid
            ppid_ = pid;

            // store one end of open pipes and close other end
            if (*pin >= 0)
            {
              wpipe_ = pin[WR];
              ::close(pin[RD]);
            }
            if (*pout >= 0)
            {
              rpipe_[rsrc_out] = pout[RD];
              ::close(pout[WR]);
            }
            if (*perr >= 0)
            {
              rpipe_[rsrc_err] = perr[RD];
              ::close(perr[WR]);
            }
          }
        }
      }
      else
      {
        // close any pipes we opened before failure
        close_fd_array(fd);
      }
      return pid;
    }

  /**
   * Closes all pipes and calls wait() to wait for the process to finish.
   * If an error occurs the error code will be set to one of the possible
   * errors for @c waitpid().
   * See your system's documentation for these errors.
   *
   * @return  @c this on successful close or @c NULL if there is no
   *          process to close or if an error occurs.
   */
  template <typename C, typename T>
    basic_pstreambuf<C,T>*
    basic_pstreambuf<C,T>::close()
    {
      const bool running = is_open();

      sync(); // this might call wait() and reap the child process

      // rather than trying to work out whether or not we need to clean up
      // just do it anyway, all cleanup functions are safe to call twice.

      destroy_buffers(pstdin|pstdout|pstderr);

      // close pipes before wait() so child gets EOF/SIGPIPE
      close_fd(wpipe_);
      close_fd_array(rpipe_);

      do
      {
        error_ = 0;
      } while (wait() == -1 && error() == EINTR);

      return running ? this : NULL;
    }

  /**
   *  Called on construction to initialise the arrays used for reading.
   */
  template <typename C, typename T>
    inline void
    basic_pstreambuf<C,T>::init_rbuffers()
    {
      rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1;
      rbuffer_[rsrc_out] = rbuffer_[rsrc_err] = NULL;
      rbufstate_[0] = rbufstate_[1] = rbufstate_[2] = NULL;
    }

  template <typename C, typename T>
    void
    basic_pstreambuf<C,T>::create_buffers(pmode mode)
    {
      if (mode & pstdin)
      {
        delete[] wbuffer_;
        wbuffer_ = new char_type[bufsz];
        this->setp(wbuffer_, wbuffer_ + bufsz);
      }
      if (mode & pstdout)
      {
        delete[] rbuffer_[rsrc_out];
        rbuffer_[rsrc_out] = new char_type[bufsz];
        rsrc_ = rsrc_out;
        this->setg(rbuffer_[rsrc_out] + pbsz, rbuffer_[rsrc_out] + pbsz,
            rbuffer_[rsrc_out] + pbsz);
      }
      if (mode & pstderr)
      {
        delete[] rbuffer_[rsrc_err];
        rbuffer_[rsrc_err] = new char_type[bufsz];
        if (!(mode & pstdout))
        {
          rsrc_ = rsrc_err;
          this->setg(rbuffer_[rsrc_err] + pbsz, rbuffer_[rsrc_err] + pbsz,
              rbuffer_[rsrc_err] + pbsz);
        }
      }
    }

  template <typename C, typename T>
    void
    basic_pstreambuf<C,T>::destroy_buffers(pmode mode)
    {
      if (mode & pstdin)
      {
        this->setp(NULL, NULL);
        delete[] wbuffer_;
        wbuffer_ = NULL;
      }
      if (mode & pstdout)
      {
        if (rsrc_ == rsrc_out)
          this->setg(NULL, NULL, NULL);
        delete[] rbuffer_[rsrc_out];
        rbuffer_[rsrc_out] = NULL;
      }
      if (mode & pstderr)
      {
        if (rsrc_ == rsrc_err)
          this->setg(NULL, NULL, NULL);
        delete[] rbuffer_[rsrc_err];
        rbuffer_[rsrc_err] = NULL;
      }
    }

  template <typename C, typename T>
    typename basic_pstreambuf<C,T>::buf_read_src
    basic_pstreambuf<C,T>::switch_read_buffer(buf_read_src src)
    {
      if (rsrc_ != src)
      {
        char_type* tmpbufstate[] = {this->eback(), this->gptr(), this->egptr()};
        this->setg(rbufstate_[0], rbufstate_[1], rbufstate_[2]);
        for (std::size_t i = 0; i < 3; ++i)
          rbufstate_[i] = tmpbufstate[i];
        rsrc_ = src;
      }
      return rsrc_;
    }

  /**
   * Suspends execution and waits for the associated process to exit, or
   * until a signal is delivered whose action is to terminate the current
   * process or to call a signal handling function. If the process has
   * already exited (i.e. it is a "zombie" process) then wait() returns
   * immediately.  Waiting for the child process causes all its system
   * resources to be freed.
   *
   * error() will return EINTR if wait() is interrupted by a signal.
   *
   * @param   nohang  true to return immediately if the process has not exited.
   * @return  1 if the process has exited and wait() has not yet been called.
   *          0 if @a nohang is true and the process has not exited yet.
   *          -1 if no process has been started or if an error occurs,
   *          in which case the error can be found using error().
   */
  template <typename C, typename T>
    int
    basic_pstreambuf<C,T>::wait(bool nohang)
    {
      int child_exited = -1;
      if (is_open())
      {
        int exit_status;
        switch(::waitpid(ppid_, &exit_status, nohang ? WNOHANG : 0))
        {
          case 0 :
            // nohang was true and process has not exited
            child_exited = 0;
            break;
          case -1 :
            error_ = errno;
            break;
          default :
            // process has exited
            ppid_ = 0;
            status_ = exit_status;
            child_exited = 1;
            // Close wpipe, would get SIGPIPE if we used it.
            destroy_buffers(pstdin);
            close_fd(wpipe_);
            // Must free read buffers and pipes on destruction
            // or next call to open()/close()
            break;
        }
      }
      return child_exited;
    }

  /**
   * Sends the specified signal to the process.  A signal can be used to
   * terminate a child process that would not exit otherwise.
   *
   * If an error occurs the error code will be set to one of the possible
   * errors for @c kill().  See your system's documentation for these errors.
   *
   * @param   signal  A signal to send to the child process.
   * @return  @c this or @c NULL if @c kill() fails.
   */
  template <typename C, typename T>
    inline basic_pstreambuf<C,T>*
    basic_pstreambuf<C,T>::kill(int signal)
    {
      basic_pstreambuf<C,T>* ret = NULL;
      if (is_open())
      {
        if (::kill(ppid_, signal))
          error_ = errno;
        else
        {
#if 0
          // TODO call exited() to check for exit and clean up? leave to user?
          if (signal==SIGTERM || signal==SIGKILL)
            this->exited();
#endif
          ret = this;
        }
      }
      return ret;
    }

  /**
   * Sends the specified signal to the process group of the child process.
   * A signal can be used to terminate a child process that would not exit
   * otherwise, or to kill the process and its own children.
   *
   * If an error occurs the error code will be set to one of the possible
   * errors for @c getpgid() or @c kill().  See your system's documentation
   * for these errors. If the child is in the current process group then
   * NULL will be returned and the error code set to EPERM.
   *
   * @param   signal  A signal to send to the child process.
   * @return  @c this on success or @c NULL on failure.
   */
  template <typename C, typename T>
    inline basic_pstreambuf<C,T>*
    basic_pstreambuf<C,T>::killpg(int signal)
    {
      basic_pstreambuf<C,T>* ret = NULL;
#ifdef _POSIX_JOB_CONTROL
      if (is_open())
      {
        pid_t pgid = ::getpgid(ppid_);
        if (pgid == -1)
          error_ = errno;
        else if (pgid == ::getpgrp())
          error_ = EPERM;  // Don't commit suicide
        else if (::killpg(pgid, signal))
          error_ = errno;
        else
          ret = this;
      }
#else
      error_ = ENOTSUP;
#endif
      return ret;
    }

  /**
   *  This function can call pstreambuf::wait() and so may change the
   *  object's state if the child process has already exited.
   *
   *  @return  True if the associated process has exited, false otherwise.
   *  @see     basic_pstreambuf<C,T>::wait()
   */
  template <typename C, typename T>
    inline bool
    basic_pstreambuf<C,T>::exited()
    {
      return ppid_ == 0 || wait(true)==1;
    }


  /**
   *  @return  The exit status of the child process, or -1 if wait()
   *           has not yet been called to wait for the child to exit.
   *  @see     basic_pstreambuf<C,T>::wait()
   */
  template <typename C, typename T>
    inline int
    basic_pstreambuf<C,T>::status() const
    {
      return status_;
    }

  /**
   *  @return  The error code of the most recently failed operation, or zero.
   */
  template <typename C, typename T>
    inline int
    basic_pstreambuf<C,T>::error() const
    {
      return error_;
    }

  /**
   *  Closes the output pipe, causing the child process to receive the
   *  end-of-file indicator on subsequent reads from its @c stdin stream.
   */
  template <typename C, typename T>
    inline void
    basic_pstreambuf<C,T>::peof()
    {
      sync();
      destroy_buffers(pstdin);
      close_fd(wpipe_);
    }

  /**
   * Unlike pstreambuf::exited(), this function will not call wait() and
   * so will not change the object's state.  This means that once a child
   * process is executed successfully this function will continue to
   * return true even after the process exits (until wait() is called.)
   *
   * @return  true if a previous call to open() succeeded and wait() has
   *          not been called and determined that the process has exited,
   *          false otherwise.
   */
  template <typename C, typename T>
    inline bool
    basic_pstreambuf<C,T>::is_open() const
    {
      return ppid_ > 0;
    }

  /**
   * Toggle the stream used for reading. If @a readerr is @c true then the
   * process' @c stderr output will be used for subsequent extractions, if
   * @a readerr is false the the process' stdout will be used.
   * @param   readerr  @c true to read @c stderr, @c false to read @c stdout.
   * @return  @c true if the requested stream is open and will be used for
   *          subsequent extractions, @c false otherwise.
   */
  template <typename C, typename T>
    inline bool
    basic_pstreambuf<C,T>::read_err(bool readerr)
    {
      buf_read_src src = readerr ? rsrc_err : rsrc_out;
      if (rpipe_[src]>=0)
      {
        switch_read_buffer(src);
        return true;
      }
      return false;
    }

  /**
   * Called when the internal character buffer is not present or is full,
   * to transfer the buffer contents to the pipe.
   *
   * @param   c  a character to be written to the pipe.
   * @return  @c traits_type::eof() if an error occurs, otherwise if @a c
   *          is not equal to @c traits_type::eof() it will be buffered and
   *          a value other than @c traits_type::eof() returned to indicate
   *          success.
   */
  template <typename C, typename T>
    typename basic_pstreambuf<C,T>::int_type
    basic_pstreambuf<C,T>::overflow(int_type c)
    {
      if (!empty_buffer())
        return traits_type::eof();
      else if (!traits_type::eq_int_type(c, traits_type::eof()))
        return this->sputc(c);
      else
        return traits_type::not_eof(c);
    }


  template <typename C, typename T>
    int
    basic_pstreambuf<C,T>::sync()
    {
      return !exited() && empty_buffer() ? 0 : -1;
    }

  /**
   * @param   s  character buffer.
   * @param   n  buffer length.
   * @return  the number of characters written.
   */
  template <typename C, typename T>
    std::streamsize
    basic_pstreambuf<C,T>::xsputn(const char_type* s, std::streamsize n)
    {
      std::streamsize done = 0;
      while (done < n)
      {
        if (std::streamsize nbuf = this->epptr() - this->pptr())
        {
          nbuf = std::min(nbuf, n - done);
          traits_type::copy(this->pptr(), s + done, nbuf);
          this->pbump(nbuf);
          done += nbuf;
        }
        else if (!empty_buffer())
          break;
      }
      return done;
    }

  /**
   * @return  true if the buffer was emptied, false otherwise.
   */
  template <typename C, typename T>
    bool
    basic_pstreambuf<C,T>::empty_buffer()
    {
      const std::streamsize count = this->pptr() - this->pbase();
      if (count > 0)
      {
        const std::streamsize written = this->write(this->wbuffer_, count);
        if (written > 0)
        {
          if (const std::streamsize unwritten = count - written)
            traits_type::move(this->pbase(), this->pbase()+written, unwritten);
          this->pbump(-written);
          return true;
        }
      }
      return false;
    }

  /**
   * Called when the internal character buffer is is empty, to re-fill it
   * from the pipe.
   *
   * @return The first available character in the buffer,
   * or @c traits_type::eof() in case of failure.
   */
  template <typename C, typename T>
    typename basic_pstreambuf<C,T>::int_type
    basic_pstreambuf<C,T>::underflow()
    {
      if (this->gptr() < this->egptr() || fill_buffer())
        return traits_type::to_int_type(*this->gptr());
      else
        return traits_type::eof();
    }

  /**
   * Attempts to make @a c available as the next character to be read by
   * @c sgetc().
   *
   * @param   c   a character to make available for extraction.
   * @return  @a c if the character can be made available,
   *          @c traits_type::eof() otherwise.
   */
  template <typename C, typename T>
    typename basic_pstreambuf<C,T>::int_type
    basic_pstreambuf<C,T>::pbackfail(int_type c)
    {
      if (this->gptr() != this->eback())
      {
        this->gbump(-1);
        if (!traits_type::eq_int_type(c, traits_type::eof()))
          *this->gptr() = traits_type::to_char_type(c);
        return traits_type::not_eof(c);
      }
      else
         return traits_type::eof();
    }

  template <typename C, typename T>
    std::streamsize
    basic_pstreambuf<C,T>::showmanyc()
    {
      int avail = 0;
      if (sizeof(char_type) == 1)
        avail = fill_buffer(true) ? this->egptr() - this->gptr() : -1;
#ifdef FIONREAD
      else
      {
        if (::ioctl(rpipe(), FIONREAD, &avail) == -1)
          avail = -1;
        else if (avail)
          avail /= sizeof(char_type);
      }
#endif
      return std::streamsize(avail);
    }

  /**
   * @return  true if the buffer was filled, false otherwise.
   */
  template <typename C, typename T>
    bool
    basic_pstreambuf<C,T>::fill_buffer(bool non_blocking)
    {
      const std::streamsize pb1 = this->gptr() - this->eback();
      const std::streamsize pb2 = pbsz;
      const std::streamsize npb = std::min(pb1, pb2);

      char_type* const rbuf = rbuffer();

      if (npb)
        traits_type::move(rbuf + pbsz - npb, this->gptr() - npb, npb);

      std::streamsize rc = -1;

      if (non_blocking)
      {
        const int flags = ::fcntl(rpipe(), F_GETFL);
        if (flags != -1)
        {
          const bool blocking = !(flags & O_NONBLOCK);
          if (blocking)
            ::fcntl(rpipe(), F_SETFL, flags | O_NONBLOCK);  // set non-blocking

          error_ = 0;
          rc = read(rbuf + pbsz, bufsz - pbsz);

          if (rc == -1 && error_ == EAGAIN)  // nothing available
            rc = 0;
          else if (rc == 0)  // EOF
            rc = -1;

          if (blocking)
            ::fcntl(rpipe(), F_SETFL, flags); // restore
        }
      }
      else
        rc = read(rbuf + pbsz, bufsz - pbsz);

      if (rc > 0 || (rc == 0 && non_blocking))
      {
        this->setg( rbuf + pbsz - npb,
                    rbuf + pbsz,
                    rbuf + pbsz + rc );
        return true;
      }
      else
      {
        this->setg(NULL, NULL, NULL);
        return false;
      }
    }

  /**
   * Writes up to @a n characters to the pipe from the buffer @a s.
   *
   * @param   s  character buffer.
   * @param   n  buffer length.
   * @return  the number of characters written.
   */
  template <typename C, typename T>
    inline std::streamsize
    basic_pstreambuf<C,T>::write(const char_type* s, std::streamsize n)
    {
      std::streamsize nwritten = 0;
      if (wpipe() >= 0)
      {
        nwritten = ::write(wpipe(), s, n * sizeof(char_type));
        if (nwritten == -1)
          error_ = errno;
        else
          nwritten /= sizeof(char_type);
      }
      return nwritten;
    }

  /**
   * Reads up to @a n characters from the pipe to the buffer @a s.
   *
   * @param   s  character buffer.
   * @param   n  buffer length.
   * @return  the number of characters read.
   */
  template <typename C, typename T>
    inline std::streamsize
    basic_pstreambuf<C,T>::read(char_type* s, std::streamsize n)
    {
      std::streamsize nread = 0;
      if (rpipe() >= 0)
      {
        nread = ::read(rpipe(), s, n * sizeof(char_type));
        if (nread == -1)
          error_ = errno;
        else
          nread /= sizeof(char_type);
      }
      return nread;
    }

  /** @return a reference to the output file descriptor */
  template <typename C, typename T>
    inline pstreams::fd_type&
    basic_pstreambuf<C,T>::wpipe()
    {
      return wpipe_;
    }

  /** @return a reference to the active input file descriptor */
  template <typename C, typename T>
    inline pstreams::fd_type&
    basic_pstreambuf<C,T>::rpipe()
    {
      return rpipe_[rsrc_];
    }

  /** @return a reference to the specified input file descriptor */
  template <typename C, typename T>
    inline pstreams::fd_type&
    basic_pstreambuf<C,T>::rpipe(buf_read_src which)
    {
      return rpipe_[which];
    }

  /** @return a pointer to the start of the active input buffer area. */
  template <typename C, typename T>
    inline typename basic_pstreambuf<C,T>::char_type*
    basic_pstreambuf<C,T>::rbuffer()
    {
      return rbuffer_[rsrc_];
    }


  /*
   * member definitions for pstream_common
   */

  /**
   * @class pstream_common
   * Abstract Base Class providing common functionality for basic_ipstream,
   * basic_opstream and basic_pstream.
   * pstream_common manages the basic_pstreambuf stream buffer that is used
   * by the derived classes to initialise an iostream class.
   */

  /** Creates an uninitialised stream. */
  template <typename C, typename T>
    inline
    pstream_common<C,T>::pstream_common()
    : std::basic_ios<C,T>(NULL)
    , command_()
    , buf_()
    {
      this->std::basic_ios<C,T>::rdbuf(&buf_);
    }

  /**
   * Initialises the stream buffer by calling
   * do_open( @a command , @a mode )
   *
   * @param cmd   a string containing a shell command.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see   do_open(const std::string&, pmode)
   */
  template <typename C, typename T>
    inline
    pstream_common<C,T>::pstream_common(const std::string& cmd, pmode mode)
    : std::basic_ios<C,T>(NULL)
    , command_(cmd)
    , buf_()
    {
      this->std::basic_ios<C,T>::rdbuf(&buf_);
      do_open(cmd, mode);
    }

  /**
   * Initialises the stream buffer by calling
   * do_open( @a file , @a argv , @a mode )
   *
   * @param file  a string containing the pathname of a program to execute.
   * @param argv  a vector of argument strings passed to the new program.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see do_open(const std::string&, const argv_type&, pmode)
   */
  template <typename C, typename T>
    inline
    pstream_common<C,T>::pstream_common( const std::string& file,
                                         const argv_type& argv,
                                         pmode mode )
    : std::basic_ios<C,T>(NULL)
    , command_(file)
    , buf_()
    {
      this->std::basic_ios<C,T>::rdbuf(&buf_);
      do_open(file, argv, mode);
    }

  /**
   * This is a pure virtual function to make @c pstream_common abstract.
   * Because it is the destructor it will be called by derived classes
   * and so must be defined.  It is also protected, to discourage use of
   * the PStreams classes through pointers or references to the base class.
   *
   * @sa If defining a pure virtual seems odd you should read
   * http://www.gotw.ca/gotw/031.htm (and the rest of the site as well!)
   */
  template <typename C, typename T>
    inline
    pstream_common<C,T>::~pstream_common()
    {
    }

  /**
   * Calls rdbuf()->open( @a command , @a mode )
   * and sets @c failbit on error.
   *
   * @param cmd   a string containing a shell command.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see   basic_pstreambuf::open(const std::string&, pmode)
   */
  template <typename C, typename T>
    inline void
    pstream_common<C,T>::do_open(const std::string& cmd, pmode mode)
    {
      if (!buf_.open((command_=cmd), mode))
        this->setstate(std::ios_base::failbit);
    }

  /**
   * Calls rdbuf()->open( @a file, @a  argv, @a mode )
   * and sets @c failbit on error.
   *
   * @param file  a string containing the pathname of a program to execute.
   * @param argv  a vector of argument strings passed to the new program.
   * @param mode  the I/O mode to use when opening the pipe.
   * @see   basic_pstreambuf::open(const std::string&, const argv_type&, pmode)
   */
  template <typename C, typename T>
    inline void
    pstream_common<C,T>::do_open( const std::string& file,
                                  const argv_type& argv,
                                  pmode mode )
    {
      if (!buf_.open((command_=file), argv, mode))
        this->setstate(std::ios_base::failbit);
    }

  /** Calls rdbuf->close() and sets @c failbit on error. */
  template <typename C, typename T>
    inline void
    pstream_common<C,T>::close()
    {
      if (!buf_.close())
        this->setstate(std::ios_base::failbit);
    }

  /**
   * @return  rdbuf()->is_open().
   * @see     basic_pstreambuf::is_open()
   */
  template <typename C, typename T>
    inline bool
    pstream_common<C,T>::is_open() const
    {
      return buf_.is_open();
    }

  /** @return a string containing the command used to initialise the stream. */
  template <typename C, typename T>
    inline const std::string&
    pstream_common<C,T>::command() const
    {
      return command_;
    }

  /** @return a pointer to the private stream buffer member. */
  // TODO  document behaviour if buffer replaced.
  template <typename C, typename T>
    inline typename pstream_common<C,T>::streambuf_type*
    pstream_common<C,T>::rdbuf() const
    {
      return const_cast<streambuf_type*>(&buf_);
    }


#if REDI_EVISCERATE_PSTREAMS
  /**
   * @def REDI_EVISCERATE_PSTREAMS
   * If this macro has a non-zero value then certain internals of the
   * @c basic_pstreambuf template class are exposed. In general this is
   * a Bad Thing, as the internal implementation is largely undocumented
   * and may be subject to change at any time, so this feature is only
   * provided because it might make PStreams useful in situations where
   * it is necessary to do Bad Things.
   */

  /**
   * @warning  This function exposes the internals of the stream buffer and
   *           should be used with caution. It is the caller's responsibility
   *           to flush streams etc. in order to clear any buffered data.
   *           The POSIX.1 function <b>fdopen</b>(3) is used to obtain the
   *           @c FILE pointers from the streambuf's private file descriptor
   *           members so consult your system's documentation for
   *           <b>fdopen</b>(3).
   *
   * @param   in    A FILE* that will refer to the process' stdin.
   * @param   out   A FILE* that will refer to the process' stdout.
   * @param   err   A FILE* that will refer to the process' stderr.
   * @return  An OR of zero or more of @c pstdin, @c pstdout, @c pstderr.
   *
   * For each open stream shared with the child process a @c FILE* is
   * obtained and assigned to the corresponding parameter. For closed
   * streams @c NULL is assigned to the parameter.
   * The return value can be tested to see which parameters should be
   * @c !NULL by masking with the corresponding @c pmode value.
   *
   * @see <b>fdopen</b>(3)
   */
  template <typename C, typename T>
    std::size_t
    basic_pstreambuf<C,T>::fopen(FILE*& in, FILE*& out, FILE*& err)
    {
      in = out = err = NULL;
      std::size_t open_files = 0;
      if (wpipe() > -1)
      {
        if ((in = ::fdopen(wpipe(), "w")))
        {
            open_files |= pstdin;
        }
      }
      if (rpipe(rsrc_out) > -1)
      {
        if ((out = ::fdopen(rpipe(rsrc_out), "r")))
        {
            open_files |= pstdout;
        }
      }
      if (rpipe(rsrc_err) > -1)
      {
        if ((err = ::fdopen(rpipe(rsrc_err), "r")))
        {
            open_files |= pstderr;
        }
      }
      return open_files;
    }

  /**
   *  @warning This function exposes the internals of the stream buffer and
   *  should be used with caution.
   *
   *  @param  in   A FILE* that will refer to the process' stdin.
   *  @param  out  A FILE* that will refer to the process' stdout.
   *  @param  err  A FILE* that will refer to the process' stderr.
   *  @return A bitwise-or of zero or more of @c pstdin, @c pstdout, @c pstderr.
   *  @see    basic_pstreambuf::fopen()
   */
  template <typename C, typename T>
    inline std::size_t
    pstream_common<C,T>::fopen(FILE*& fin, FILE*& fout, FILE*& ferr)
    {
      return buf_.fopen(fin, fout, ferr);
    }

#endif // REDI_EVISCERATE_PSTREAMS


} // namespace redi

/**
 * @mainpage PStreams Reference
 * @htmlinclude mainpage.html
 */

#endif  // REDI_PSTREAM_H_SEEN
#endif  // WIN32
// vim: ts=2 sw=2 expandtab


// ===== END pstream.h =====

using namespace std;


void PrintErrorAndQuit(const string sErrorString)
{
    cout << sErrorString << endl;
    exit(1);
}

template <typename T> inline T getmin(const T &a, const T &b)
{
    return b<a?b:a;
}

template <class A> void NewArray(A *** array, int Narray1, int Narray2)
{
    *array=new A* [Narray1];
    for(int i=0; i<Narray1; i++) *(*array+i)=new A [Narray2];
}

template <class A> void DeleteArray(A *** array, int Narray)
{
    for(int i=0; i<Narray; i++)
        if(*(*array+i)) delete [] *(*array+i);
    if(Narray) delete [] (*array);
    (*array)=NULL;
}

string AAmap(char A)
{
    if (A=='A') return "ALA";
    if (A=='B') return "ASX";
    if (A=='C') return "CYS";
    if (A=='D') return "ASP";
    if (A=='E') return "GLU";
    if (A=='F') return "PHE";
    if (A=='G') return "GLY";
    if (A=='H') return "HIS";
    if (A=='I') return "ILE";
    if (A=='K') return "LYS";
    if (A=='L') return "LEU";
    if (A=='M') return "MET";
    if (A=='N') return "ASN";
    if (A=='O') return "PYL";
    if (A=='P') return "PRO";
    if (A=='Q') return "GLN";
    if (A=='R') return "ARG";
    if (A=='S') return "SER";
    if (A=='T') return "THR";
    if (A=='U') return "SEC";
    if (A=='V') return "VAL";
    if (A=='W') return "TRP";    
    if (A=='Y') return "TYR";
    if (A=='Z') return "GLX";
    if ('a'<=A && A<='z') return "  "+string(1,char(toupper(A)));
    return "UNK";
}

char AAmap(const string &AA)
{
    if (AA.compare("ALA")==0 || AA.compare("DAL")==0) return 'A';
    if (AA.compare("ASX")==0) return 'B';
    if (AA.compare("CYS")==0 || AA.compare("DCY")==0) return 'C';
    if (AA.compare("ASP")==0 || AA.compare("DAS")==0) return 'D';
    if (AA.compare("GLU")==0 || AA.compare("DGL")==0) return 'E';
    if (AA.compare("PHE")==0 || AA.compare("DPN")==0) return 'F';
    if (AA.compare("GLY")==0) return 'G';
    if (AA.compare("HIS")==0 || AA.compare("DHI")==0) return 'H';
    if (AA.compare("ILE")==0 || AA.compare("DIL")==0) return 'I';
    if (AA.compare("LYS")==0 || AA.compare("DLY")==0) return 'K';
    if (AA.compare("LEU")==0 || AA.compare("DLE")==0) return 'L';
    if (AA.compare("MET")==0 || AA.compare("MED")==0 ||
        AA.compare("MSE")==0) return 'M';
    if (AA.compare("ASN")==0 || AA.compare("DSG")==0) return 'N';
    if (AA.compare("PYL")==0) return 'O';
    if (AA.compare("PRO")==0 || AA.compare("DPR")==0) return 'P';
    if (AA.compare("GLN")==0 || AA.compare("DGN")==0) return 'Q';
    if (AA.compare("ARG")==0 || AA.compare("DAR")==0) return 'R';
    if (AA.compare("SER")==0 || AA.compare("DSN")==0) return 'S';
    if (AA.compare("THR")==0 || AA.compare("DTH")==0) return 'T';
    if (AA.compare("SEC")==0) return 'U';
    if (AA.compare("VAL")==0 || AA.compare("DVA")==0) return 'V';
    if (AA.compare("TRP")==0 || AA.compare("DTR")==0) return 'W';    
    if (AA.compare("TYR")==0 || AA.compare("DTY")==0) return 'Y';
    if (AA.compare("GLX")==0) return 'Z';

    if (AA.compare(0,2," D")==0) return tolower(AA[2]);
    if (AA.compare(0,2,"  ")==0) return tolower(AA[2]);
    return 'X';
}

/* split a long string into vectors by whitespace 
 * line          - input string
 * line_vec      - output vector 
 * delimiter     - delimiter */
void split(const string &line, vector<string> &line_vec,
    const char delimiter=' ')
{
    bool within_word = false;
    for (size_t pos=0;pos<line.size();pos++)
    {
        if (line[pos]==delimiter)
        {
            within_word = false;
            continue;
        }
        if (!within_word)
        {
            within_word = true;
            line_vec.push_back("");
        }
        line_vec.back()+=line[pos];
    }
}

/* strip white space at the begining or end of string */
string Trim(const string &inputString)
{
    string result = inputString;
    int idxBegin = inputString.find_first_not_of(" \n\r\t");
    int idxEnd = inputString.find_last_not_of(" \n\r\t");
    if (idxBegin >= 0 && idxEnd >= 0)
        result = inputString.substr(idxBegin, idxEnd + 1 - idxBegin);
    return result;
}

size_t get_PDB_lines(const string filename,
    vector<vector<string> >&PDB_lines, vector<string> &chainID_list,
    vector<int> &mol_vec, const int ter_opt, const int infmt_opt,
    const string atom_opt, const bool autojustify, const int split_opt, 
    const int het_opt, const vector<string>&chain2parse,
    const vector<string>&model2parse)
{
    size_t i=0; // resi i.e. atom index
    string line;
    char chainID=0;
    string resi="";
    bool select_atom=false;
    size_t model_idx=0;
    vector<string> tmp_str_vec;

    int compress_type=0; // uncompressed file
    ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
    ifstream fin_gz;
#else
    redi::ipstream fin_gz; // if file is compressed
    if (filename.size()>=3 && 
        filename.substr(filename.size()-3,3)==".gz")
    {
        fin_gz.open("gunzip -c '"+filename+"'");
        compress_type=1;
    }
    else if (filename.size()>=4 && 
        filename.substr(filename.size()-4,4)==".bz2")
    {
        fin_gz.open("bzcat '"+filename+"'");
        compress_type=2;
    }
    else
#endif
    {
        if (filename=="-") compress_type=-1;
        else fin.open(filename.c_str());
    }

    if (infmt_opt==0||infmt_opt==-1) // PDB format
    {
        map<string,char> aa3to1;
        aa3to1["  A"]=aa3to1[" DA"]='a';
        aa3to1["  C"]=aa3to1[" DC"]='c';
        aa3to1["  G"]=aa3to1[" DG"]='g';
        aa3to1["  U"]=aa3to1["PSU"]='u';
        aa3to1["  I"]=aa3to1[" DI"]='i';
        aa3to1["  T"]='t';
        aa3to1["ALA"]='A';
        aa3to1["CYS"]='C';
        aa3to1["ASP"]='D';
        aa3to1["GLU"]='E';
        aa3to1["PHE"]='F';
        aa3to1["GLY"]='G';
        aa3to1["HIS"]='H';
        aa3to1["ILE"]='I';
        aa3to1["LYS"]='K';
        aa3to1["LEU"]='L';
        aa3to1["MET"]=aa3to1["MSE"]='M';
        aa3to1["ASN"]='N';
        aa3to1["PRO"]='P';
        aa3to1["GLN"]='Q';
        aa3to1["ARG"]='R';
        aa3to1["SER"]='S';
        aa3to1["THR"]='T';
        aa3to1["VAL"]='V'; 
        aa3to1["TRP"]='W';
        aa3to1["TYR"]='Y';
        aa3to1["ASX"]='B';
        aa3to1["GLX"]='Z';
        aa3to1["SEC"]='U';
        aa3to1["PYL"]='O';


        string atom;
        string resn;
        string model_index="1";
        map<string, char> alt_id_dict; // resi -> alt_id
        string resi_chain;
        while ((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))
        {
            if  (compress_type==-1) getline(cin, line);
            else if (compress_type) getline(fin_gz, line);
            else                    getline(fin, line);
            if (infmt_opt==-1 && (line.compare(0,5,"loop_")==0 || 
                                  line.compare(0,1,"#")==0)) // PDBx/mmCIF
                return get_PDB_lines(filename,PDB_lines,chainID_list, mol_vec,
                    ter_opt, 3, atom_opt, autojustify, split_opt,het_opt,
                    chain2parse, model2parse);
            if (model2parse.size())
            {
                if (line.size()>=6 && line.compare(0,5,"MODEL")==0)
                    model_index=Trim(line.substr(5,9));
                else if (find(model2parse.begin(),model2parse.end(),
                    model_index)==model2parse.end()) continue;
            }
            if (i > 0)
            {
                if      (ter_opt>=1 && line.compare(0,3,"END")==0) break;
                else if (ter_opt>=3 && line.compare(0,3,"TER")==0) break;
            }
            if (line.compare(0,3,"END")==0)
            {
                if (split_opt) chainID=0;
                map<string, char> ().swap(alt_id_dict);
            }
            if (line.size()>=54 && //(line[16]==' ' || line[16]=='A') && 
               ((line.compare(0, 6, "ATOM  ")==0) || 
                (line.compare(0, 6, "HETATM")==0 && het_opt==1) ||
                (line.compare(0, 6, "HETATM")==0 && het_opt==2 && 
                 line.compare(17,3, "MSE")==0)))
            {
                atom=line.substr(12,4);
                if (autojustify)
                {
                    resn=line.substr(17,3);
                    if (aa3to1.count(resn))
                    {
                        atom=Trim(atom);
                        if (atom.size())
                        {
                            if (atom.size()>=2 && atom[atom.size()-1]=='*')
                                atom=atom.substr(0,atom.size()-1)+"'";
                            if (atom.size()==1) atom=" "+atom+"  ";
                            else if (atom.size()==2) atom=" "+atom+" ";
                            else if (atom.size()==3) atom=" "+atom;
                        }
                    }
                }
                if (atom_opt=="auto")
                {
                    if (line[17]==' ' && (line[18]=='D'||line[18]==' '))
                         select_atom=(atom==" C3'");
                    else select_atom=(atom==" CA ");
                }
                else if (atom_opt=="PC4'")
                {
                    if (line[17]==' ' && (line[18]=='D'||line[18]==' '))
                         select_atom=(atom==" P  ")||(atom==" C4'");
                    else select_atom=(atom==" CA ");
                }
                else if (atom_opt==" CB ")
                {
                    if (line.compare(17,3, "GLY")==0)
                         select_atom=(atom==" CA "); // glycine has no CB
                    else select_atom=(atom==" CB ");
                }
                else     select_atom=(atom==atom_opt);
                if (select_atom)
                {
                    resi_chain=line.substr(21,6);
                    if (alt_id_dict.count(resi_chain)==0)
                        alt_id_dict[resi_chain]=line[16];
                    else if (alt_id_dict[resi_chain]!=line[16]) continue;

                    if (chain2parse.size() && ( (line[21]==' ' && 
                        find(chain2parse.begin(),chain2parse.end(), "_"
                            )==chain2parse.end())|| (line[21]!=' ' && 
                        find(chain2parse.begin(), chain2parse.end(),
                            string(1,line[21]))==chain2parse.end()))) continue;
                        
                    if (!chainID)
                    {
                        chainID=line[21];
                        model_idx++;
                        stringstream i8_stream;
                        i=0;
                        if (split_opt==2) // split by chain
                        {
                            if (chainID==' ')
                            {
                                if (ter_opt>=1) i8_stream << ":_";
                                else i8_stream<<':'<<model_idx<<",_";
                            }
                            else
                            {
                                if (ter_opt>=1) i8_stream << ':' << chainID;
                                else i8_stream<<':'<<model_idx<<','<<chainID;
                            }
                            chainID_list.push_back(i8_stream.str());
                        }
                        else if (split_opt==1) // split by model
                        {
                            i8_stream << ':' << model_idx;
                            chainID_list.push_back(i8_stream.str());
                        }
                        PDB_lines.push_back(tmp_str_vec);
                        mol_vec.push_back(0);
                    }
                    else if (ter_opt>=2 && chainID!=line[21]) break;
                    if (split_opt==2 && chainID!=line[21])
                    {
                        chainID=line[21];
                        i=0;
                        stringstream i8_stream;
                        if (chainID==' ')
                        {
                            if (ter_opt>=1) i8_stream << ":_";
                            else i8_stream<<':'<<model_idx<<",_";
                        }
                        else
                        {
                            if (ter_opt>=1) i8_stream << ':' << chainID;
                            else i8_stream<<':'<<model_idx<<','<<chainID;
                        }
                        chainID_list.push_back(i8_stream.str());
                        PDB_lines.push_back(tmp_str_vec);
                        mol_vec.push_back(0);
                    }

                    if (resi==line.substr(22,5) && atom_opt!="PC4'")
                        cerr<<"Warning! Duplicated residue "<<resi<<endl;
                    resi=line.substr(22,5); // including insertion code

                    PDB_lines.back().push_back(line);
                    if (line[17]==' ' && (line[18]=='D'||line[18]==' ')) mol_vec.back()++;
                    else mol_vec.back()--;
                    i++;
                }
            }
        }

        map<string,char>().swap(aa3to1);
        map<string, char>().swap(alt_id_dict); // resi -> alt_id
        string ().swap(resi_chain);
    }
    else if (infmt_opt==1) // SPICKER format
    {
        size_t L=0;
        float x,y,z;
        stringstream i8_stream;
        while ((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))
        {
            if  (compress_type==-1)
            {
                cin>>L>>x>>y>>z;
                getline(cin, line);
                if (!cin.good()) break;
            }
            else if (compress_type)
            {
                fin_gz>>L>>x>>y>>z;
                getline(fin_gz, line);
                if (!fin_gz.good()) break;
            }
            else
            {
                fin   >>L>>x>>y>>z;
                getline(fin, line);
                if (!fin.good()) break;
            }
            model_idx++;
            stringstream i8_stream;
            i8_stream << ':' << model_idx;
            chainID_list.push_back(i8_stream.str());
            PDB_lines.push_back(tmp_str_vec);
            mol_vec.push_back(0);
            for (i=0;i<L;i++)
            {
                if  (compress_type==-1) cin>>x>>y>>z;
                else if (compress_type) fin_gz>>x>>y>>z;
                else                    fin   >>x>>y>>z;
                i8_stream<<"ATOM   "<<setw(4)<<i+1<<"  CA  UNK  "<<setw(4)
                    <<i+1<<"    "<<setiosflags(ios::fixed)<<setprecision(3)
                    <<setw(8)<<x<<setw(8)<<y<<setw(8)<<z;
                line=i8_stream.str();
                i8_stream.str(string());
                PDB_lines.back().push_back(line);
            }
            if  (compress_type==-1) getline(cin, line);
            else if (compress_type) getline(fin_gz, line);
            else                    getline(fin, line);
        }
    }
    else if (infmt_opt==2) // xyz format
    {
        size_t L=0;
        stringstream i8_stream;
        while ((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))
        {
            if (compress_type==-1)  getline(cin, line);
            else if (compress_type) getline(fin_gz, line);
            else                    getline(fin, line);
            L=atoi(line.c_str());
            if (compress_type==-1)  getline(cin, line);
            else if (compress_type) getline(fin_gz, line);
            else                    getline(fin, line);
            for (i=0;i<line.size();i++)
                if (line[i]==' '||line[i]=='\t') break;
            if (!((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))) break;
            chainID_list.push_back(':'+line.substr(0,i));
            PDB_lines.push_back(tmp_str_vec);
            mol_vec.push_back(0);
            for (i=0;i<L;i++)
            {
                if (compress_type==-1)  getline(cin, line);
                else if (compress_type) getline(fin_gz, line);
                else                    getline(fin, line);
                i8_stream<<"ATOM   "<<setw(4)<<i+1<<"  CA  "
                    <<AAmap(line[0])<<"  "<<setw(4)<<i+1<<"    "
                    <<line.substr(2,8)<<line.substr(11,8)<<line.substr(20,8);
                line=i8_stream.str();
                i8_stream.str(string());
                PDB_lines.back().push_back(line);
                if (line[0]>='a' && line[0]<='z') mol_vec.back()++; // RNA
                else mol_vec.back()--;
            }
        }
    }
    else if (infmt_opt==3) // PDBx/mmCIF format
    {
        bool loop_ = false; // not reading following content
        map<string,int> _atom_site;
        int atom_site_pos;
        vector<string> line_vec;
        string alt_id=".";  // alternative location indicator
        string asym_id="."; // this is similar to chainID, except that
                            // chainID is char while asym_id is a string
                            // with possibly multiple char
        string prev_asym_id="";
        string AA="";       // residue name
        string atom="";
        string prev_resi="";
        string model_index=""; // the same as model_idx but type is string
        stringstream i8_stream;
        map<string, string> alt_id_dict; // resi -> alt_id
        string resi_chain;
        while ((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))
        {
            if (compress_type==-1)  getline(cin, line);
            else if (compress_type) getline(fin_gz, line);
            else                    getline(fin, line);
            if (line.size()==0) continue;
            if (loop_) loop_ = (line.size()>=2)?(line.compare(0,2,"# ")):(line.compare(0,1,"#"));
            if (!loop_)
            {
                if (line.compare(0,5,"loop_")) continue;
                while(1)
                {
                    if (compress_type==-1)
                    {
                        if (cin.good()) getline(cin, line);
                        else PrintErrorAndQuit("ERROR! Unexpected end of -");
                    }
                    else if (compress_type)
                    {
                        if (fin_gz.good()) getline(fin_gz, line);
                        else PrintErrorAndQuit("ERROR! Unexpected end of "+filename);
                    }
                    else
                    {
                        if (fin.good()) getline(fin, line);
                        else PrintErrorAndQuit("ERROR! Unexpected end of "+filename);
                    }
                    if (line.size()) break;
                }
                if (line.compare(0,11,"_atom_site.")) continue;

                loop_=true;
                _atom_site.clear();
                atom_site_pos=0;
                _atom_site[Trim(line.substr(11))]=atom_site_pos;

                while(1)
                {
                    if  (compress_type==-1) getline(cin, line);
                    else if (compress_type) getline(fin_gz, line);
                    else                    getline(fin, line);
                    if (line.size()==0) continue;
                    if (line.compare(0,11,"_atom_site.")) break;
                    _atom_site[Trim(line.substr(11))]=++atom_site_pos;
                }


                if (_atom_site.count("group_PDB")*
                    _atom_site.count("label_atom_id")*
                    _atom_site.count("label_comp_id")*
                   (_atom_site.count("auth_asym_id")+
                    _atom_site.count("label_asym_id"))*
                   (_atom_site.count("auth_seq_id")+
                    _atom_site.count("label_seq_id"))*
                    _atom_site.count("Cartn_x")*
                    _atom_site.count("Cartn_y")*
                    _atom_site.count("Cartn_z")==0)
                {
                    loop_ = false;
                    cerr<<"Warning! Missing one of the following _atom_site data items: group_PDB, label_atom_id, label_comp_id, auth_asym_id/label_asym_id, auth_seq_id/label_seq_id, Cartn_x, Cartn_y, Cartn_z"<<endl;
                    continue;
                }
            }

            line_vec.clear();
            split(line,line_vec);
            if ((line_vec[_atom_site["group_PDB"]]!="ATOM" &&
                 line_vec[_atom_site["group_PDB"]]!="HETATM") ||
                (line_vec[_atom_site["group_PDB"]]=="HETATM" &&
                 (het_opt==0 || 
                 (het_opt==2 && line_vec[_atom_site["label_comp_id"]]!="MSE")))
                ) continue;
            
            atom=line_vec[_atom_site["label_atom_id"]];
            if (atom[0]=='"') atom=atom.substr(1);
            if (atom.size() && atom[atom.size()-1]=='"')
                atom=atom.substr(0,atom.size()-1);
            if (atom.size()>=3 && atom[0]=='\'' && atom[atom.size()-1]=='\'')
                atom=atom.substr(1,atom.size()-2);
            if (atom.size()==0) continue;
            if      (atom.size()==1) atom=" "+atom+"  ";
            else if (atom.size()==2) atom=" "+atom+" "; // wrong for sidechain H
            else if (atom.size()==3) atom=" "+atom;
            else if (atom.size()>=5) continue;

            AA=line_vec[_atom_site["label_comp_id"]]; // residue name
            if      (AA.size()==1) AA="  "+AA;
            else if (AA.size()==2) AA=" " +AA;
            else if (AA.size()>=4) continue;

            if (atom_opt=="auto")
            {
                if (AA[0]==' ' && (AA[1]=='D'||AA[1]==' ')) // DNA || RNA
                     select_atom=(atom==" C3'");
                else select_atom=(atom==" CA ");
            }
            else if (atom_opt=="PC4'")
            {
                if (line[17]==' ' && (line[18]=='D'||line[18]==' '))
                     select_atom=(line.compare(12,4," P  ")==0
                              )||(line.compare(12,4," C4'")==0);
                else select_atom=(line.compare(12,4," CA ")==0);
            }
            else     select_atom=(atom==atom_opt);

            if (!select_atom) continue;

            if (_atom_site.count("auth_asym_id"))
                 asym_id=line_vec[_atom_site["auth_asym_id"]];
            else asym_id=line_vec[_atom_site["label_asym_id"]];
            if (asym_id==".") asym_id=" ";

            if (chain2parse.size() && ( (asym_id==" " && 
                find(chain2parse.begin(),chain2parse.end(), "_"
                )==chain2parse.end())|| (asym_id!=" " && 
                find(chain2parse.begin(), chain2parse.end(),asym_id
                )==chain2parse.end()))) continue;

            if (model2parse.size() && _atom_site.count("pdbx_PDB_model_num") &&
                find(model2parse.begin(), model2parse.end(),
                    line_vec[_atom_site["pdbx_PDB_model_num"]]
                    )==model2parse.end()) continue;

            if (_atom_site.count("pdbx_PDB_model_num") && 
                model_index!=line_vec[_atom_site["pdbx_PDB_model_num"]])
            {
                model_index=line_vec[_atom_site["pdbx_PDB_model_num"]];

                if (PDB_lines.size() && ter_opt>=1) break;
                if (PDB_lines.size()==0 || split_opt>=1)
                {
                    PDB_lines.push_back(tmp_str_vec);
                    mol_vec.push_back(0);
                    prev_asym_id=asym_id;

                    if (split_opt==1 && ter_opt==0) chainID_list.push_back(
                        ':'+model_index);
                    else if (split_opt==2 && ter_opt==0)
                        chainID_list.push_back(':'+model_index+','+asym_id);
                    else //if (split_opt==2 && ter_opt==1)
                        chainID_list.push_back(':'+asym_id);
                    //else
                        //chainID_list.push_back("");
                }
                map<string, string>().swap(alt_id_dict);
            }
            
            if (_atom_site.count("auth_seq_id"))
                 resi=line_vec[_atom_site["auth_seq_id"]];
            else resi=line_vec[_atom_site["label_seq_id"]];
            if (_atom_site.count("pdbx_PDB_ins_code") && 
                line_vec[_atom_site["pdbx_PDB_ins_code"]]!="?")
                resi+=line_vec[_atom_site["pdbx_PDB_ins_code"]][0];
            else resi+=" ";
            
            if (_atom_site.count("label_alt_id")) // in 39.4 % of entries
            {
                alt_id=line_vec[_atom_site["label_alt_id"]];
                resi_chain=asym_id+resi;
                if (alt_id_dict.count(resi_chain)==0)
                    alt_id_dict[resi_chain]=alt_id;
                else if (alt_id_dict.count(resi_chain) && alt_id!=alt_id_dict[resi_chain])
                    continue;
                //if (alt_id!="." && alt_id!="A") continue;
            }

            if (prev_asym_id!=asym_id)
            {
                if (prev_asym_id!="" && ter_opt>=2) break;
                if (split_opt>=2)
                {
                    PDB_lines.push_back(tmp_str_vec);
                    mol_vec.push_back(0);

                    if (split_opt==1 && ter_opt==0) chainID_list.push_back(
                        ':'+model_index);
                    else if (split_opt==2 && ter_opt==0)
                        chainID_list.push_back(':'+model_index+','+asym_id);
                    else //if (split_opt==2 && ter_opt==1)
                        chainID_list.push_back(':'+asym_id);
                    //else
                        //chainID_list.push_back("");
                }
            }
            if (prev_asym_id!=asym_id) prev_asym_id=asym_id;

            if (AA[0]==' ' && (AA[1]=='D'||AA[1]==' ')) mol_vec.back()++;
            else mol_vec.back()--;

            if (prev_resi==resi && atom_opt!="PC4'")
                cerr<<"Warning! Duplicated residue "<<resi<<endl;
            prev_resi=resi;

            i++;
            i8_stream<<"ATOM  "
                <<setw(5)<<i<<" "<<atom<<" "<<AA<<" "<<asym_id[0]
                <<setw(5)<<resi.substr(0,5)<<"   "
                <<setw(8)<<line_vec[_atom_site["Cartn_x"]].substr(0,8)
                <<setw(8)<<line_vec[_atom_site["Cartn_y"]].substr(0,8)
                <<setw(8)<<line_vec[_atom_site["Cartn_z"]].substr(0,8);
            PDB_lines.back().push_back(i8_stream.str());
            i8_stream.str(string());
        }
        _atom_site.clear();
        line_vec.clear();
        alt_id.clear();
        asym_id.clear();
        AA.clear();
        map<string, string>().swap(alt_id_dict);
        resi_chain.clear();
    }

    if      (compress_type>=1) fin_gz.close();
    else if (compress_type==0) fin.close();
    line.clear();
    if (!split_opt) chainID_list.push_back("");
    return PDB_lines.size();
}

/* read fasta file from filename. sequence is stored into FASTA_lines
 * while sequence name is stored into chainID_list.
 * if ter_opt >=1, only read the first sequence.
 * if ter_opt ==0, read all sequences.
 * if split_opt >=1 and ter_opt ==0, each sequence is a separate entry.
 * if split_opt ==0 and ter_opt ==0, all sequences are combined into one */
size_t get_FASTA_lines(const string filename,
    vector<vector<string> >&FASTA_lines, vector<string> &chainID_list,
    vector<int> &mol_vec, const int ter_opt=3, const int split_opt=0)
{
    string line;
    vector<string> tmp_str_vec;
    size_t l;
    
    int compress_type=0; // uncompressed file
    ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
    ifstream fin_gz;
#else
    redi::ipstream fin_gz; // if file is compressed
    if (filename.size()>=3 && 
        filename.substr(filename.size()-3,3)==".gz")
    {
        fin_gz.open("gunzip -c '"+filename+"'");
        compress_type=1;
    }
    else if (filename.size()>=4 && 
        filename.substr(filename.size()-4,4)==".bz2")
    {
        fin_gz.open("bzcat '"+filename+"'");
        compress_type=2;
    }
    else 
#endif
    {
        if (filename=="-") compress_type=-1;
        else fin.open(filename.c_str());
    }

    while ((compress_type==-1)?cin.good():(compress_type?fin_gz.good():fin.good()))
    {
        if  (compress_type==-1) getline(cin, line);
        else if (compress_type) getline(fin_gz, line);
        else                    getline(fin, line);

        if (line.size()==0 || line[0]=='#') continue;

        if (line[0]=='>')
        {
            if (FASTA_lines.size())
            {
                if (ter_opt) break;
                if (split_opt==0) continue;
            }
            FASTA_lines.push_back(tmp_str_vec);
            FASTA_lines.back().push_back("");
            mol_vec.push_back(0);
            if (ter_opt==0 && split_opt)
            {
                line[0]=':';
                chainID_list.push_back(line);
            }
            else chainID_list.push_back("");
        }
        else
        {
            FASTA_lines.back()[0]+=line;
            for (l=0;l<line.size();l++) mol_vec.back()+=
                ('a'<=line[l] && line[l]<='z')-('A'<=line[l] && line[l]<='Z');
        }
    }

    line.clear();
    if      (compress_type>=1) fin_gz.close();
    else if (compress_type==0) fin.close();
    return FASTA_lines.size();
}

int read_PDB(const vector<string> &PDB_lines, double **a, char *seq,
    vector<string> &resi_vec, const int read_resi)
{
    size_t i;
    for (i=0;i<PDB_lines.size();i++)
    {
        a[i][0] = atof(PDB_lines[i].substr(30, 8).c_str());
        a[i][1] = atof(PDB_lines[i].substr(38, 8).c_str());
        a[i][2] = atof(PDB_lines[i].substr(46, 8).c_str());
        seq[i]  = AAmap(PDB_lines[i].substr(17, 3));

        if (read_resi>=2) resi_vec.push_back(PDB_lines[i].substr(22,5)+
                                             PDB_lines[i][21]);
        if (read_resi==1) resi_vec.push_back(PDB_lines[i].substr(22,5));
    }
    seq[i]='\0'; 
    return i;
}

double dist(double x[3], double y[3])
{
    double d1=x[0]-y[0];
    double d2=x[1]-y[1];
    double d3=x[2]-y[2];
 
    return (d1*d1 + d2*d2 + d3*d3);
}

double dot(double *a, double *b)
{
    return (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
}

void transform(double t[3], double u[3][3], double *x, double *x1)
{
    x1[0]=t[0]+dot(&u[0][0], x);
    x1[1]=t[1]+dot(&u[1][0], x);
    x1[2]=t[2]+dot(&u[2][0], x);
}

void do_rotation(double **x, double **x1, int len, double t[3], double u[3][3])
{
    for(int i=0; i<len; i++)
    {
        transform(t, u, &x[i][0], &x1[i][0]);
    }    
}

/* read user specified pairwise alignment from 'fname_lign' to 'sequence'.
 * This function should only be called by main function, as it will
 * terminate a program if wrong alignment is given */
void read_user_alignment(vector<string>&sequence, const string &fname_lign,
    const int i_opt)
{
    if (fname_lign == "")
        PrintErrorAndQuit("Please provide a file name for option -i!");
    // open alignment file
    int n_p = 0;// number of structures in alignment file
    string line;
    
    ifstream fileIn(fname_lign.c_str());
    if (fileIn.is_open())
    {
        while (fileIn.good())
        {
            getline(fileIn, line);
            if (line.compare(0, 1, ">") == 0)// Flag for a new structure
            {
                if (n_p >= 2) break;
                sequence.push_back("");
                n_p++;
            }
            else if (n_p > 0 && line!="") sequence.back()+=line;
        }
        fileIn.close();
    }
    else PrintErrorAndQuit("ERROR! Alignment file does not exist.");
    
    if (n_p < 2)
        PrintErrorAndQuit("ERROR: Fasta format is wrong, two proteins should be included.");
    if (sequence[0].size() != sequence[1].size())
        PrintErrorAndQuit("ERROR! FASTA file is wrong. The length in alignment should be equal for the two aligned proteins.");
    if (i_opt==3)
    {
        int aligned_resNum=0;
        for (size_t i=0;i<sequence[0].size();i++)
            aligned_resNum+=(sequence[0][i]!='-' && sequence[1][i]!='-');
        if (aligned_resNum<3)
            PrintErrorAndQuit("ERROR! Superposition is undefined for <3 aligned residues.");
    }
    line.clear();
    return;
}


inline bool isfile(const string& filename)
{
    if (FILE *fp = fopen(filename.c_str(), "r"))
    {
        fclose(fp);
        return true;
    }
    else return false;
}

/* read list of entries from 'name' to 'chain_list'.
 * dir_opt is the folder name (prefix).
 * suffix_opt is the file name extension (suffix_opt).
 * This function should only be called by main function, as it will
 * terminate a program if wrong alignment is given */
void file2chainlist(vector<string>&chain_list, const string &name,
    const string &dir_opt, const string &suffix_opt)
{
    ifstream fp(name.c_str());
    if (! fp.is_open())
        PrintErrorAndQuit(("Can not open file: "+name+'\n').c_str());
    string line;
    string filename;
    int a,b;
    string sep;
    while (fp.good())
    {
        getline(fp, line);
        if (! line.size()) continue;
        line=Trim(line);
        for (a=0;a<=2;a++)
        {
            if      (a==0) sep="";
            else if (a==1) sep="/";
            else if (a==2) sep="\\";
                
            filename=dir_opt+sep+line+suffix_opt;
            if (isfile(filename)) break;
            if (suffix_opt.size())
            {
                filename=dir_opt+sep+line;
                if (isfile(filename)) break;
            }
            else
            {
                filename=dir_opt+sep+line+".pdb";
                if (isfile(filename)) break;
                filename=dir_opt+sep+line+".cif";
                if (isfile(filename)) break;
            }
            filename.clear();
        }

        if (filename.size()==0)
        {
            filename=dir_opt+line+suffix_opt;
            cerr<<"WARNING! "<<filename<<" does not exist"<<endl;
        }
        else chain_list.push_back(filename);
        line.clear();
        filename.clear();
    }
    fp.close();
}

void file2chainpairlist(vector<string>&chain1_list, vector<string>&chain2_list,
    const string &name, const string &dirpair_opt, const string &suffix_opt)
{
    ifstream fp(name.c_str());
    if (! fp.is_open())
        PrintErrorAndQuit(("Can not open file: "+name+'\n').c_str());
    string line;
    string filename;
    int a,b;
    size_t i;
    string sep,filename1,filename2;
    vector<string> line_vec;
    while (fp.good())
    {
        getline(fp, line);
        if (! line.size()) continue;
        line=Trim(line);
        split(line, line_vec, '\t');
        if (line_vec.size()==2)
        {
            filename1=line_vec[0];
            filename2=line_vec[1];
            for (i=0;i<2;i++) line_vec[i].clear(); line_vec.clear();
        }
        else
        {
            for (i=0;i<line_vec.size();i++) line_vec[i].clear(); line_vec.clear();
            split(line, line_vec, ' ');
            if (line_vec.size()==2)
            {
                filename1=line_vec[0];
                filename2=line_vec[1];
                for (i=0;i<2;i++) line_vec[i].clear(); line_vec.clear();
            }
            else
            {
                cerr<<"WARNING! not a chain pair: "<<line<<endl;
                for (i=0;i<line_vec.size();i++) line_vec[i].clear(); line_vec.clear();
                continue;
            }
        }

        filename.clear();
        for (a=0;a<=2;a++)
        {
            if      (a==0) sep="";
            else if (a==1) sep="/";
            else if (a==2) sep="\\";
                
            filename=dirpair_opt+sep+filename1+suffix_opt;
            if (isfile(filename)) break;
            if (suffix_opt.size())
            {
                filename=dirpair_opt+sep+line;
                if (isfile(filename)) break;
            }
            else
            {
                filename=dirpair_opt+sep+line+".pdb";
                if (isfile(filename)) break;
                filename=dirpair_opt+sep+line+".cif";
                if (isfile(filename)) break;
            }
            filename.clear();
        }

        if (filename.size()==0)
        {
            filename=dirpair_opt+filename1+suffix_opt;
            cerr<<"WARNING! "<<filename<<" does not exist"<<endl;
            continue;
        }
        else
        {
            filename1=filename; 
            filename.clear();
        }

        for (a=0;a<=2;a++)
        {
            if      (a==0) sep="";
            else if (a==1) sep="/";
            else if (a==2) sep="\\";
                
            filename=dirpair_opt+sep+filename2+suffix_opt;
            if (isfile(filename)) break;
            if (suffix_opt.size())
            {
                filename=dirpair_opt+sep+line;
                if (isfile(filename)) break;
            }
            else
            {
                filename=dirpair_opt+sep+line+".pdb";
                if (isfile(filename)) break;
                filename=dirpair_opt+sep+line+".cif";
                if (isfile(filename)) break;
            }
            filename.clear();
        }

        if (filename.size()==0)
        {
            filename=dirpair_opt+filename2+suffix_opt;
            cerr<<"WARNING! "<<filename<<" does not exist"<<endl;
            continue;
        }
        else
        {
            filename2=filename; 
            filename.clear();
        }

        chain1_list.push_back(filename1);
        chain2_list.push_back(filename2);
        line.clear();
        filename.clear();
    }
    fp.close();
}

#endif

// ===== END basic_fun.h =====

void parameter_set4search(const int xlen, const int ylen,
                          double &D0_MIN, double &Lnorm,
                          double &score_d8, double &d0, double &d0_search, double &dcu0)
{
    // parameter initialization for searching: D0_MIN, Lnorm, d0, d0_search, score_d8
    D0_MIN = 0.5;
    dcu0 = 4.25; // update 3.85-->4.25

    Lnorm = getmin(xlen, ylen); // normalize TMscore by this in searching
    if (Lnorm <= 19)            // update 15-->19
        d0 = 0.168;             // update 0.5-->0.168
    else
        d0 = (1.24 * pow((Lnorm * 1.0 - 15), 1.0 / 3) - 1.8);
    D0_MIN = d0 + 0.8; // this should be moved to above
    d0 = D0_MIN;       // update: best for search

    d0_search = d0;
    if (d0_search > 8)
        d0_search = 8;
    if (d0_search < 4.5)
        d0_search = 4.5;

    score_d8 = 1.5 * pow(Lnorm * 1.0, 0.3) + 3.5; // remove pairs with dis>d8 during search & final
}

void parameter_set4final_C3prime(const double len, double &D0_MIN,
                                 double &Lnorm, double &d0, double &d0_search)
{
    D0_MIN = 0.3;

    Lnorm = len; // normalize TMscore by this in searching
    if (Lnorm <= 11)
        d0 = 0.3;
    else if (Lnorm > 11 && Lnorm <= 15)
        d0 = 0.4;
    else if (Lnorm > 15 && Lnorm <= 19)
        d0 = 0.5;
    else if (Lnorm > 19 && Lnorm <= 23)
        d0 = 0.6;
    else if (Lnorm > 23 && Lnorm < 30)
        d0 = 0.7;
    else
        d0 = (0.6 * pow((Lnorm * 1.0 - 0.5), 1.0 / 2) - 2.5);

    d0_search = d0;
    if (d0_search > 8)
        d0_search = 8;
    if (d0_search < 4.5)
        d0_search = 4.5;
}

void parameter_set4final(const double len, double &D0_MIN, double &Lnorm,
                         double &d0, double &d0_search, const int mol_type)
{
    if (mol_type > 0) // RNA
    {
        parameter_set4final_C3prime(len, D0_MIN, Lnorm,
                                    d0, d0_search);
        return;
    }
    D0_MIN = 0.5;

    Lnorm = len; // normalize TMscore by this in searching
    if (Lnorm <= 21)
        d0 = 0.5;
    else
        d0 = (1.24 * pow((Lnorm * 1.0 - 15), 1.0 / 3) - 1.8);
    if (d0 < D0_MIN)
        d0 = D0_MIN;
    d0_search = d0;
    if (d0_search > 8)
        d0_search = 8;
    if (d0_search < 4.5)
        d0_search = 4.5;
}

void parameter_set4scale(const int len, const double d_s, double &Lnorm,
                         double &d0, double &d0_search)
{
    d0 = d_s;
    Lnorm = len; // normalize TMscore by this in searching
    d0_search = d0;
    if (d0_search > 8)
        d0_search = 8;
    if (d0_search < 4.5)
        d0_search = 4.5;
}

// ===== END param_set.h =====
// Original: #include "NW.h"
// ===== BEGIN NW.h =====
/* Partial implementation of Needleman-Wunsch (NW) dynamic programming for
 * global alignment. The three NWDP_TM functions below are not complete
 * implementation of NW algorithm because gap jumping in the standard Gotoh
 * algorithm is not considered. Since the gap opening and gap extension is
 * the same, this is not a problem. This code was exploited in TM-align
 * because it is about 1.5 times faster than a complete NW implementation.
 * Nevertheless, if gap opening != gap extension shall be implemented in
 * the future, the Gotoh algorithm must be implemented. In rare scenarios,
 * it is also possible to have asymmetric alignment (i.e. 
 * TMalign A.pdb B.pdb and TMalign B.pdb A.pdb have different TM_A and TM_B
 * values) caused by the NWPD_TM implement.
 */

/* Input: score[1:len1, 1:len2], and gap_open
 * Output: j2i[1:len2] \in {1:len1} U {-1}
 * path[0:len1, 0:len2]=1,2,3, from diagonal, horizontal, vertical */
void NWDP_TM(double **score, bool **path, double **val,
    int len1, int len2, double gap_open, int j2i[])
{

    int i, j;
    double h, v, d;

    //initialization
    for(i=0; i<=len1; i++)
    {
        val[i][0]=0;
        //val[i][0]=i*gap_open;
        path[i][0]=false; //not from diagonal
    }

    for(j=0; j<=len2; j++)
    {
        val[0][j]=0;
        //val[0][j]=j*gap_open;
        path[0][j]=false; //not from diagonal
        j2i[j]=-1;    //all are not aligned, only use j2i[1:len2]
    }      


    //decide matrix and path
    for(i=1; i<=len1; i++)
    {
        for(j=1; j<=len2; j++)
        {
            d=val[i-1][j-1]+score[i][j]; //diagonal

            //symbol insertion in horizontal (= a gap in vertical)
            h=val[i-1][j];
            if(path[i-1][j]) h += gap_open; //aligned in last position

            //symbol insertion in vertical
            v=val[i][j-1];
            if(path[i][j-1]) v += gap_open; //aligned in last position


            if(d>=h && d>=v)
            {
                path[i][j]=true; //from diagonal
                val[i][j]=d;
            }
            else 
            {
                path[i][j]=false; //from horizontal
                if(v>=h) val[i][j]=v;
                else val[i][j]=h;
            }
        } //for i
    } //for j

    //trace back to extract the alignment
    i=len1;
    j=len2;
    while(i>0 && j>0)
    {
        if(path[i][j]) //from diagonal
        {
            j2i[j-1]=i-1;
            i--;
            j--;
        }
        else 
        {
            h=val[i-1][j];
            if(path[i-1][j]) h +=gap_open;

            v=val[i][j-1];
            if(path[i][j-1]) v +=gap_open;

            if(v>=h) j--;
            else i--;
        }
    }
}

/* Input: vectors x, y, rotation matrix t, u, scale factor d02, and gap_open
 * Output: j2i[1:len2] \in {1:len1} U {-1}
 * path[0:len1, 0:len2]=1,2,3, from diagonal, horizontal, vertical */
void NWDP_TM(bool **path, double **val, double **x, double **y,
    int len1, int len2, double t[3], double u[3][3],
    double d02, double gap_open, int j2i[])
{
    int i, j;
    double h, v, d;

    //initialization. use old val[i][0] and val[0][j] initialization
    //to minimize difference from TMalign fortran version
    for(i=0; i<=len1; i++)
    {
        val[i][0]=0;
        //val[i][0]=i*gap_open;
        path[i][0]=false; //not from diagonal
    }

    for(j=0; j<=len2; j++)
    {
        val[0][j]=0;
        //val[0][j]=j*gap_open;
        path[0][j]=false; //not from diagonal
        j2i[j]=-1;    //all are not aligned, only use j2i[1:len2]
    }      
    double xx[3], dij;


    //decide matrix and path
    for(i=1; i<=len1; i++)
    {
        transform(t, u, &x[i-1][0], xx);
        for(j=1; j<=len2; j++)
        {
            dij=dist(xx, &y[j-1][0]);    
            d=val[i-1][j-1] +  1.0/(1+dij/d02);

            //symbol insertion in horizontal (= a gap in vertical)
            h=val[i-1][j];
            if(path[i-1][j]) h += gap_open; //aligned in last position

            //symbol insertion in vertical
            v=val[i][j-1];
            if(path[i][j-1]) v += gap_open; //aligned in last position


            if(d>=h && d>=v)
            {
                path[i][j]=true; //from diagonal
                val[i][j]=d;
            }
            else 
            {
                path[i][j]=false; //from horizontal
                if(v>=h) val[i][j]=v;
                else val[i][j]=h;
            }
        } //for i
    } //for j

    //trace back to extract the alignment
    i=len1;
    j=len2;
    while(i>0 && j>0)
    {
        if(path[i][j]) //from diagonal
        {
            j2i[j-1]=i-1;
            i--;
            j--;
        }
        else 
        {
            h=val[i-1][j];
            if(path[i-1][j]) h +=gap_open;

            v=val[i][j-1];
            if(path[i][j-1]) v +=gap_open;

            if(v>=h) j--;
            else i--;
        }
    }
}

/* This is the same as the previous NWDP_TM, except for the lack of rotation
 * Input: vectors x, y, scale factor d02, and gap_open
 * Output: j2i[1:len2] \in {1:len1} U {-1}
 * path[0:len1, 0:len2]=1,2,3, from diagonal, horizontal, vertical */
void NWDP_SE(bool **path, double **val, double **x, double **y,
    int len1, int len2, double d02, double gap_open, int j2i[])
{
    int i, j;
    double h, v, d;

    for(i=0; i<=len1; i++)
    {
        val[i][0]=0;
        path[i][0]=false; //not from diagonal
    }

    for(j=0; j<=len2; j++)
    {
        val[0][j]=0;
        path[0][j]=false; //not from diagonal
        j2i[j]=-1;    //all are not aligned, only use j2i[1:len2]
    }      
    double dij;

    //decide matrix and path
    for(i=1; i<=len1; i++)
    {
        for(j=1; j<=len2; j++)
        {
            dij=dist(&x[i-1][0], &y[j-1][0]);    
            d=val[i-1][j-1] +  1.0/(1+dij/d02);

            //symbol insertion in horizontal (= a gap in vertical)
            h=val[i-1][j];
            if(path[i-1][j]) h += gap_open; //aligned in last position

            //symbol insertion in vertical
            v=val[i][j-1];
            if(path[i][j-1]) v += gap_open; //aligned in last position


            if(d>=h && d>=v)
            {
                path[i][j]=true; //from diagonal
                val[i][j]=d;
            }
            else 
            {
                path[i][j]=false; //from horizontal
                if(v>=h) val[i][j]=v;
                else val[i][j]=h;
            }
        } //for i
    } //for j

    //trace back to extract the alignment
    i=len1;
    j=len2;
    while(i>0 && j>0)
    {
        if(path[i][j]) //from diagonal
        {
            j2i[j-1]=i-1;
            i--;
            j--;
        }
        else 
        {
            h=val[i-1][j];
            if(path[i-1][j]) h +=gap_open;

            v=val[i][j-1];
            if(path[i][j-1]) v +=gap_open;

            if(v>=h) j--;
            else i--;
        }
    }
}

void NWDP_SE(bool **path, double **val, double **x, double **y,
    int len1, int len2, double d02, double gap_open, int j2i[],
    const int hinge)
{
    if (hinge==0)
    {
        NWDP_SE(path, val, x, y, len1, len2, d02, gap_open, j2i);
        return;
    }
    int i, j;
    double h, v, d;

    int L=(len2>len1)?len2:len1;
    int int_min=L*(gap_open-1);

    for (i=0; i<=len1; i++)
    {
        for (j=0; j<=len2; j++)
        {
            val[i][j]=0;
            path[i][j]=false;
        }
    }

    /* fill in old j2i */
    int k=0;
    for (j=0; j<len2; j++)
    {
        i=j2i[j];
        if (i<0) continue;
        path[i+1][j+1]=true;
        val[i+1][j+1]=0;
    }

    double dij;

    //decide matrix and path
    for(i=1; i<=len1; i++)
    {
        for(j=1; j<=len2; j++)
        {
            dij=0;
            if (path[i][j]==false) dij=dist(&x[i-1][0], &y[j-1][0]);    
            d=val[i-1][j-1] +  1.0/(1+dij/d02);

            //symbol insertion in horizontal (= a gap in vertical)
            h=val[i-1][j];
            if(path[i-1][j]) h += gap_open; //aligned in last position

            //symbol insertion in vertical
            v=val[i][j-1];
            if(path[i][j-1]) v += gap_open; //aligned in last position


            if(d>=h && d>=v && val[i][j]==0)
            {
                path[i][j]=true; //from diagonal
                val[i][j]=d;
            }
            else 
            {
                path[i][j]=false; //from horizontal
                if(v>=h) val[i][j]=v;
                else val[i][j]=h;
            }
        } //for i
    } //for j

    //trace back to extract the alignment
    for (j=0;j<=len2;j++) j2i[j]=-1;
    i=len1;
    j=len2;
    while(i>0 && j>0)
    {
        if(path[i][j]) //from diagonal
        {
            j2i[j-1]=i-1;
            i--;
            j--;
        }
        else 
        {
            h=val[i-1][j];
            if(path[i-1][j]) h +=gap_open;

            v=val[i][j-1];
            if(path[i][j-1]) v +=gap_open;

            if(v>=h) j--;
            else i--;
        }
    }
}

/* +ss
 * Input: secondary structure secx, secy, and gap_open
 * Output: j2i[1:len2] \in {1:len1} U {-1}
 * path[0:len1, 0:len2]=1,2,3, from diagonal, horizontal, vertical */
void NWDP_TM(bool **path, double **val, const char *secx, const char *secy,
    const int len1, const int len2, const double gap_open, int j2i[])
{

    int i, j;
    double h, v, d;

    //initialization
    for(i=0; i<=len1; i++)
    {
        val[i][0]=0;
        //val[i][0]=i*gap_open;
        path[i][0]=false; //not from diagonal
    }

    for(j=0; j<=len2; j++)
    {
        val[0][j]=0;
        //val[0][j]=j*gap_open;
        path[0][j]=false; //not from diagonal
        j2i[j]=-1;    //all are not aligned, only use j2i[1:len2]
    }      

    //decide matrix and path
    for(i=1; i<=len1; i++)
    {
        for(j=1; j<=len2; j++)
        {
            d=val[i-1][j-1] + 1.0*(secx[i-1]==secy[j-1]);

            //symbol insertion in horizontal (= a gap in vertical)
            h=val[i-1][j];
            if(path[i-1][j]) h += gap_open; //aligned in last position

            //symbol insertion in vertical
            v=val[i][j-1];
            if(path[i][j-1]) v += gap_open; //aligned in last position

            if(d>=h && d>=v)
            {
                path[i][j]=true; //from diagonal
                val[i][j]=d;
            }
            else 
            {
                path[i][j]=false; //from horizontal
                if(v>=h) val[i][j]=v;
                else val[i][j]=h;
            }
        } //for i
    } //for j

    //trace back to extract the alignment
    i=len1;
    j=len2;
    while(i>0 && j>0)
    {
        if(path[i][j]) //from diagonal
        {
            j2i[j-1]=i-1;
            i--;
            j--;
        }
        else 
        {
            h=val[i-1][j];
            if(path[i-1][j]) h +=gap_open;

            v=val[i][j-1];
            if(path[i][j-1]) v +=gap_open;

            if(v>=h) j--;
            else i--;
        }
    }
}

// ===== END NW.h =====
// Original: #include "Kabsch.h"
// ===== BEGIN Kabsch.h =====
/**************************************************************************
Implemetation of Kabsch algoritm for finding the best rotation matrix
---------------------------------------------------------------------------
x    - x(i,m) are coordinates of atom m in set x            (input)
y    - y(i,m) are coordinates of atom m in set y            (input)
n    - n is number of atom pairs                            (input)
mode  - 0:calculate rms only                                (input)
1:calculate u,t only                                (takes medium)
2:calculate rms,u,t                                 (takes longer)
rms   - sum of w*(ux+t-y)**2 over all atom pairs            (output)
u    - u(i,j) is   rotation  matrix for best superposition  (output)
t    - t(i)   is translation vector for best superposition  (output)
**************************************************************************/
bool Kabsch(double **x, double **y, int n, int mode, double *rms,
            double t[3], double u[3][3])
{
    int i, j, m, m1, l, k;
    double e0, rms1, d, h, g;
    double cth, sth, sqrth, p, det, sigma;
    double xc[3], yc[3];
    double a[3][3], b[3][3], r[3][3], e[3], rr[6], ss[6];
    double sqrt3 = 1.73205080756888, tol = 0.01;
    int ip[] = {0, 1, 3, 1, 2, 4, 3, 4, 5};
    int ip2312[] = {1, 2, 0, 1};

    int a_failed = 0, b_failed = 0;
    double epsilon = 0.00000001;

    // initialization
    *rms = 0;
    rms1 = 0;
    e0 = 0;
    double c1[3], c2[3];
    double s1[3], s2[3];
    double sx[3], sy[3], sz[3];
    for (i = 0; i < 3; i++)
    {
        s1[i] = 0.0;
        s2[i] = 0.0;

        sx[i] = 0.0;
        sy[i] = 0.0;
        sz[i] = 0.0;
    }

    for (i = 0; i < 3; i++)
    {
        xc[i] = 0.0;
        yc[i] = 0.0;
        t[i] = 0.0;
        for (j = 0; j < 3; j++)
        {
            u[i][j] = 0.0;
            r[i][j] = 0.0;
            a[i][j] = 0.0;
            if (i == j)
            {
                u[i][j] = 1.0;
                a[i][j] = 1.0;
            }
        }
    }

    if (n < 1)
        return false;

    // compute centers for vector sets x, y
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < 3; j++)
        {
            c1[j] = x[i][j];
            c2[j] = y[i][j];

            s1[j] += c1[j];
            s2[j] += c2[j];
        }

        for (j = 0; j < 3; j++)
        {
            sx[j] += c1[0] * c2[j];
            sy[j] += c1[1] * c2[j];
            sz[j] += c1[2] * c2[j];
        }
    }
    for (i = 0; i < 3; i++)
    {
        xc[i] = s1[i] / n;
        yc[i] = s2[i] / n;
    }
    if (mode == 2 || mode == 0)
        for (int mm = 0; mm < n; mm++)
            for (int nn = 0; nn < 3; nn++)
                e0 += (x[mm][nn] - xc[nn]) * (x[mm][nn] - xc[nn]) +
                      (y[mm][nn] - yc[nn]) * (y[mm][nn] - yc[nn]);
    for (j = 0; j < 3; j++)
    {
        r[j][0] = sx[j] - s1[0] * s2[j] / n;
        r[j][1] = sy[j] - s1[1] * s2[j] / n;
        r[j][2] = sz[j] - s1[2] * s2[j] / n;
    }

    // compute determinant of matrix r
    det = r[0][0] * (r[1][1] * r[2][2] - r[1][2] * r[2][1]) - r[0][1] * (r[1][0] * r[2][2] - r[1][2] * r[2][0]) + r[0][2] * (r[1][0] * r[2][1] - r[1][1] * r[2][0]);
    sigma = det;

    // compute tras(r)*r
    m = 0;
    for (j = 0; j < 3; j++)
    {
        for (i = 0; i <= j; i++)
        {
            rr[m] = r[0][i] * r[0][j] + r[1][i] * r[1][j] + r[2][i] * r[2][j];
            m++;
        }
    }

    double spur = (rr[0] + rr[2] + rr[5]) / 3.0;
    double cof = (((((rr[2] * rr[5] - rr[4] * rr[4]) + rr[0] * rr[5]) - rr[3] * rr[3]) + rr[0] * rr[2]) - rr[1] * rr[1]) / 3.0;
    det = det * det;

    for (i = 0; i < 3; i++)
        e[i] = spur;

    if (spur > 0)
    {
        d = spur * spur;
        h = d - cof;
        g = (spur * cof - det) / 2.0 - spur * h;

        if (h > 0)
        {
            sqrth = sqrt(h);
            d = h * h * h - g * g;
            if (d < 0.0)
                d = 0.0;
            d = atan2(sqrt(d), -g) / 3.0;
            cth = sqrth * cos(d);
            sth = sqrth * sqrt3 * sin(d);
            e[0] = (spur + cth) + cth;
            e[1] = (spur - cth) + sth;
            e[2] = (spur - cth) - sth;

            if (mode != 0)
            { // compute a
                for (l = 0; l < 3; l = l + 2)
                {
                    d = e[l];
                    ss[0] = (d - rr[2]) * (d - rr[5]) - rr[4] * rr[4];
                    ss[1] = (d - rr[5]) * rr[1] + rr[3] * rr[4];
                    ss[2] = (d - rr[0]) * (d - rr[5]) - rr[3] * rr[3];
                    ss[3] = (d - rr[2]) * rr[3] + rr[1] * rr[4];
                    ss[4] = (d - rr[0]) * rr[4] + rr[1] * rr[3];
                    ss[5] = (d - rr[0]) * (d - rr[2]) - rr[1] * rr[1];

                    if (fabs(ss[0]) <= epsilon)
                        ss[0] = 0.0;
                    if (fabs(ss[1]) <= epsilon)
                        ss[1] = 0.0;
                    if (fabs(ss[2]) <= epsilon)
                        ss[2] = 0.0;
                    if (fabs(ss[3]) <= epsilon)
                        ss[3] = 0.0;
                    if (fabs(ss[4]) <= epsilon)
                        ss[4] = 0.0;
                    if (fabs(ss[5]) <= epsilon)
                        ss[5] = 0.0;

                    if (fabs(ss[0]) >= fabs(ss[2]))
                    {
                        j = 0;
                        if (fabs(ss[0]) < fabs(ss[5]))
                            j = 2;
                    }
                    else if (fabs(ss[2]) >= fabs(ss[5]))
                        j = 1;
                    else
                        j = 2;

                    d = 0.0;
                    j = 3 * j;
                    for (i = 0; i < 3; i++)
                    {
                        k = ip[i + j];
                        a[i][l] = ss[k];
                        d = d + ss[k] * ss[k];
                    }

                    // if( d > 0.0 ) d = 1.0 / sqrt(d);
                    if (d > epsilon)
                        d = 1.0 / sqrt(d);
                    else
                        d = 0.0;
                    for (i = 0; i < 3; i++)
                        a[i][l] = a[i][l] * d;
                } // for l

                d = a[0][0] * a[0][2] + a[1][0] * a[1][2] + a[2][0] * a[2][2];
                if ((e[0] - e[1]) > (e[1] - e[2]))
                {
                    m1 = 2;
                    m = 0;
                }
                else
                {
                    m1 = 0;
                    m = 2;
                }
                p = 0;
                for (i = 0; i < 3; i++)
                {
                    a[i][m1] = a[i][m1] - d * a[i][m];
                    p = p + a[i][m1] * a[i][m1];
                }
                if (p <= tol)
                {
                    p = 1.0;
                    for (i = 0; i < 3; i++)
                    {
                        if (p < fabs(a[i][m]))
                            continue;
                        p = fabs(a[i][m]);
                        j = i;
                    }
                    k = ip2312[j];
                    l = ip2312[j + 1];
                    p = sqrt(a[k][m] * a[k][m] + a[l][m] * a[l][m]);
                    if (p > tol)
                    {
                        a[j][m1] = 0.0;
                        a[k][m1] = -a[l][m] / p;
                        a[l][m1] = a[k][m] / p;
                    }
                    else
                        a_failed = 1;
                } // if p<=tol
                else
                {
                    p = 1.0 / sqrt(p);
                    for (i = 0; i < 3; i++)
                        a[i][m1] = a[i][m1] * p;
                } // else p<=tol
                if (a_failed != 1)
                {
                    a[0][1] = a[1][2] * a[2][0] - a[1][0] * a[2][2];
                    a[1][1] = a[2][2] * a[0][0] - a[2][0] * a[0][2];
                    a[2][1] = a[0][2] * a[1][0] - a[0][0] * a[1][2];
                }
            } // if(mode!=0)
        } // h>0

        // compute b anyway
        if (mode != 0 && a_failed != 1) // a is computed correctly
        {
            // compute b
            for (l = 0; l < 2; l++)
            {
                d = 0.0;
                for (i = 0; i < 3; i++)
                {
                    b[i][l] = r[i][0] * a[0][l] +
                              r[i][1] * a[1][l] + r[i][2] * a[2][l];
                    d = d + b[i][l] * b[i][l];
                }
                // if( d > 0 ) d = 1.0 / sqrt(d);
                if (d > epsilon)
                    d = 1.0 / sqrt(d);
                else
                    d = 0.0;
                for (i = 0; i < 3; i++)
                    b[i][l] = b[i][l] * d;
            }
            d = b[0][0] * b[0][1] + b[1][0] * b[1][1] + b[2][0] * b[2][1];
            p = 0.0;

            for (i = 0; i < 3; i++)
            {
                b[i][1] = b[i][1] - d * b[i][0];
                p += b[i][1] * b[i][1];
            }

            if (p <= tol)
            {
                p = 1.0;
                for (i = 0; i < 3; i++)
                {
                    if (p < fabs(b[i][0]))
                        continue;
                    p = fabs(b[i][0]);
                    j = i;
                }
                k = ip2312[j];
                l = ip2312[j + 1];
                p = sqrt(b[k][0] * b[k][0] + b[l][0] * b[l][0]);
                if (p > tol)
                {
                    b[j][1] = 0.0;
                    b[k][1] = -b[l][0] / p;
                    b[l][1] = b[k][0] / p;
                }
                else
                    b_failed = 1;
            } // if( p <= tol )
            else
            {
                p = 1.0 / sqrt(p);
                for (i = 0; i < 3; i++)
                    b[i][1] = b[i][1] * p;
            }
            if (b_failed != 1)
            {
                b[0][2] = b[1][0] * b[2][1] - b[1][1] * b[2][0];
                b[1][2] = b[2][0] * b[0][1] - b[2][1] * b[0][0];
                b[2][2] = b[0][0] * b[1][1] - b[0][1] * b[1][0];
                // compute u
                for (i = 0; i < 3; i++)
                    for (j = 0; j < 3; j++)
                        u[i][j] = b[i][0] * a[j][0] +
                                  b[i][1] * a[j][1] + b[i][2] * a[j][2];
            }

            // compute t
            for (i = 0; i < 3; i++)
                t[i] = ((yc[i] - u[i][0] * xc[0]) - u[i][1] * xc[1]) -
                       u[i][2] * xc[2];
        } // if(mode!=0 && a_failed!=1)
    } // spur>0
    else // just compute t and errors
    {
        // compute t
        for (i = 0; i < 3; i++)
            t[i] = ((yc[i] - u[i][0] * xc[0]) - u[i][1] * xc[1]) -
                   u[i][2] * xc[2];
    } // else spur>0

    // compute rms
    for (i = 0; i < 3; i++)
    {
        if (e[i] < 0)
            e[i] = 0;
        e[i] = sqrt(e[i]);
    }
    d = e[2];
    if (sigma < 0.0)
        d = -d;
    d = (d + e[1]) + e[0];

    if (mode == 2 || mode == 0)
    {
        rms1 = (e0 - d) - d;
        if (rms1 < 0.0)
            rms1 = 0.0;
    }

    *rms = rms1;
    return true;
}

// ===== END Kabsch.h =====
// Original: #include "NWalign.h"
// ===== BEGIN NWalign.h =====
/* header for Needleman-Wunsch global sequence alignment */
#ifndef NWalign_H
#define NWalign_H 1

// Original: #include "basic_fun.h"
// Already expanded: basic_fun.h
// Original: #include "BLOSUM.h"
// ===== BEGIN BLOSUM.h =====
#ifndef TMalign_BLOSUM_H
#define TMalign_BLOSUM_H 1

/* This matrix contains two scoring matrices: 
 * [1] BLOSUM62 for protein is defined for upper case letters:
 *     ABCDEFGHIKLMNOPQRSTVWXYZ* excluding J
 *     The original BLOSUM does not have O (PYL) and U (SEC).
 *     In this matrix, OU values are copied from K and C, respectively.
 * [2] BLASTN for RNA/DNA is defined for lower case letters:
 *     acgtu where matching (including t vs u) is 2 and mismatching is -3 */

const int BLOSUM[128][128]={
//0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 51 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
//0                   \a \b \t \n \v \f \t                                                       ' ' |  "  #  $  %  &  '  (  )  *  +  ,  -  .  /  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~ DEL
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//0   '\0'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//1   SOH
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//2   STX
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//3   ETX
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//4   EOT
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//5   ENQ
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//6   ACK
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//7   '\a'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//8   '\b'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//9   '\t'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//10  '\n'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//11  '\v'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//12  '\f'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//13  '\r'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//14  SO
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//15  SI
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//16  DLE
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//17  DC1
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//18  DC2
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//19  DC3
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//20  DC4
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//21  NAK
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//22  SYN
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//23  ETB
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//24  CAN
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//25  EM
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//26  SUB
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//27  ESC
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//28  FS
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//29  GS
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//30  RS
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//31  US
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//32  ' '
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//33  !    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//34  "    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//35  #    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//36  $    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//37  %    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//38  &    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//39  '    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//40  (    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//41  )    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4,-4,-4,-4,-4,-4,-4,-4,-4, 0,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//42  *
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//43  +    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//44  ,    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//45  -    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//46  .    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//47  /    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//48  0    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//49  1    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//50  2    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//51  3    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//52  4    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//53  5    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//54  6    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//55  7    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//56  8    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//57  9    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//58  :    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//59  ;    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//60  <    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//61  =    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//62  >    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//63  ?    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//64  @    
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,-2, 0,-2,-1,-2, 0,-2,-1, 0,-1,-1,-1,-2,-1,-1,-1,-1, 1, 0, 0, 0,-3, 0,-2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//65  A
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2, 4,-3, 4, 1,-3,-1, 0,-3, 0, 0,-4,-3, 3, 0,-2, 0,-1, 0,-1,-3,-3,-4,-1,-3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//66  B
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 9,-3,-4,-2,-3,-3,-1, 0,-3,-1,-1,-3,-3,-3,-3,-3,-1,-1, 9,-1,-2,-2,-2,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//67  C
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2, 4,-3, 6, 2,-3,-1,-1,-3, 0,-1,-4,-3, 1,-1,-1, 0,-2, 0,-1,-3,-3,-4,-1,-3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//68  D
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 1,-4, 2, 5,-3,-2, 0,-3, 0, 1,-3,-2, 0, 1,-1, 2, 0, 0,-1,-4,-2,-3,-1,-2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//69  E
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2,-3,-2,-3,-3, 6,-3,-1, 0, 0,-3, 0, 0,-3,-3,-4,-3,-3,-2,-2,-2,-1, 1,-1, 3,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//70  F
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-3,-1,-2,-3, 6,-2,-4, 0,-2,-4,-3, 0,-2,-2,-2,-2, 0,-2,-3,-3,-2,-1,-3,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//71  G
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2, 0,-3,-1, 0,-1,-2, 8,-3, 0,-1,-3,-2, 1,-1,-2, 0, 0,-1,-2,-3,-3,-2,-1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//72  H
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-3,-1,-3,-3, 0,-4,-3, 4, 0,-3, 2, 1,-3,-3,-3,-3,-3,-2,-1,-1, 3,-3,-1,-1,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//73  I
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//74  J
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0,-3,-1, 1,-3,-2,-1,-3, 0, 5,-2,-1, 0, 5,-1, 1, 2, 0,-1,-3,-2,-3,-1,-2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//75  K
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-4,-1,-4,-3, 0,-4,-3, 2, 0,-2, 4, 2,-3,-2,-3,-2,-2,-2,-1,-1, 1,-2,-1,-1,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//76  L
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-3,-1,-3,-2, 0,-3,-2, 1, 0,-1, 2, 5,-2,-1,-2, 0,-1,-1,-1,-1, 1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//77  M
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2, 3,-3, 1, 0,-3, 0, 1,-3, 0, 0,-3,-2, 6, 0,-2, 0, 0, 1, 0,-3,-3,-4,-1,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//78  N
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0,-3,-1, 1,-3,-2,-1,-3, 0, 5,-2,-1, 0, 5,-1, 1, 2, 0,-1,-3,-2,-3,-1,-2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//79  O
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-2,-3,-1,-1,-4,-2,-2,-3, 0,-1,-3,-2,-2,-1, 7,-1,-2,-1,-1,-3,-2,-4,-2,-3,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//80  P
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0,-3, 0, 2,-3,-2, 0,-3, 0, 1,-2, 0, 0, 1,-1, 5, 1, 0,-1,-3,-2,-2,-1,-1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//81  Q
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-1,-3,-2, 0,-3,-2, 0,-3, 0, 2,-2,-1, 0, 2,-2, 1, 5,-1,-1,-3,-3,-3,-1,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//82  R
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,-1, 0, 0,-2, 0,-1,-2, 0, 0,-2,-1, 1, 0,-1, 0,-1, 4, 1,-1,-2,-3, 0,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//83  S
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-1,-1,-1,-2,-2,-2,-1, 0,-1,-1,-1, 0,-1,-1,-1,-1, 1, 5,-1, 0,-2, 0,-2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//84  T
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 9,-3,-4,-2,-3,-3,-1, 0,-3,-1,-1,-3,-3,-3,-3,-3,-1,-1, 9,-1,-2,-2,-2,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//85  U
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3,-1,-3,-2,-1,-3,-3, 3, 0,-2, 1, 1,-3,-2,-2,-2,-3,-2, 0,-1, 4,-3,-1,-1,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//86  V
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3,-4,-2,-4,-3, 1,-2,-2,-3, 0,-3,-2,-1,-4,-3,-4,-2,-3,-3,-2,-2,-3,11,-2, 2,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//87  W
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,-2,-1,-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-2,-1,-1, 0, 0,-2,-1,-2,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//88  X
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-2,-3,-2,-3,-2, 3,-3, 2,-1, 0,-2,-1,-1,-2,-2,-3,-1,-2,-2,-2,-2,-1, 2,-1, 7,-2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//89  Y
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 1,-3, 1, 4,-3,-2, 0,-3, 0, 1,-3,-1, 0, 1,-1, 3, 0, 0,-1,-3,-2,-3,-1,-2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//90  Z
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//91  [
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//92  '\'
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//93  ]
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//94  ^
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//95  _
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//96  `
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,-3, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//97  a
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//98  b
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0, 2, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//99  c
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//100 d
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//101 e
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//102 f
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0,-3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//103 g
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//104 h
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//105 i
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//106 j
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//107 k
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//108 l
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//109 m
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//110 n
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//111 o
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//112 p
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//113 q
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//114 r
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//115 s
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0,-3, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//116 t
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-3, 0,-3, 0, 0, 0,-3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//117 u
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//118 v
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//119 w
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//120 x
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//121 y
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//122 z
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//123 {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//124 |
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//125 }
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//126 ~
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},//127 DEL
};

#endif

// ===== END BLOSUM.h =====

#define MAX(A,B) ((A)>(B)?(A):(B))

using namespace std;

const int gapopen_blosum62=-11;
const int gapext_blosum62=-1;

const int gapopen_blastn=-15; //-5;
const int gapext_blastn =-4;  //-2;

/* initialize matrix in gotoh algorithm */
void init_gotoh_mat(int **S, int **JumpH, int **JumpV, int **P,
    int **H, int **V, const int xlen, const int ylen, const int gapopen,
    const int gapext, const int glocal=0, const int alt_init=1)
{
    // fill first row/colum of JumpH,jumpV and path matrix P
    int i,j;
    for (i=0;i<xlen+1;i++)
        for (j=0;j<ylen+1;j++)
            H[i][j]=V[i][j]=P[i][j]=JumpH[i][j]=JumpV[i][j]=0;
    for (i=0;i<xlen+1;i++)
    {
        if (glocal<2) P[i][0]=4; // -
        JumpV[i][0]=i;
    }
    for (j=0;j<ylen+1;j++)
    {
        if (glocal<1) P[0][j]=2; // |
        JumpH[0][j]=j;
    }
    if (glocal<2) for (i=1;i<xlen+1;i++) S[i][0]=gapopen+gapext*(i-1);
    if (glocal<1) for (j=1;j<ylen+1;j++) S[0][j]=gapopen+gapext*(j-1);
    if (alt_init==0)
    {
        for (i=1;i<xlen+1;i++) H[i][0]=gapopen+gapext*(i-1);
        for (j=1;j<ylen+1;j++) V[0][j]=gapopen+gapext*(j-1);
    }
    else
    {
        if (glocal<2) for (i=1;i<xlen+1;i++) V[i][0]=gapopen+gapext*(i-1);
        if (glocal<1) for (j=1;j<ylen+1;j++) H[0][j]=gapopen+gapext*(j-1);
        for (i=0;i<xlen+1;i++) H[i][0]=-99999; // INT_MIN cause bug on ubuntu
        for (j=0;j<ylen+1;j++) V[0][j]=-99999; // INT_MIN;
    }
}

/* locate the cell with highest alignment score. reset path after
 * the cell to zero */
void find_highest_align_score( int **S, int **P,
    int &aln_score, const int xlen,const int ylen)
{
    // locate the cell with highest alignment score
    int max_aln_i=xlen;
    int max_aln_j=ylen;
    int i,j;
    for (i=0;i<xlen+1;i++)
    {
        for (j=0;j<ylen+1;j++)
        {
            if (S[i][j]>=aln_score)
            {
                max_aln_i=i;
                max_aln_j=j;
                aln_score=S[i][j];
            }
        }
    }

    // reset all path after [max_aln_i][max_aln_j]
    for (i=max_aln_i+1;i<xlen+1;i++) for (j=0;j<ylen+1;j++) P[i][j]=0;
    for (i=0;i<xlen+1;i++) for (j=max_aln_j+1;j<ylen+1;j++) P[i][j]=0;
}

/* calculate dynamic programming matrix using gotoh algorithm
 * S     - cumulative scorefor each cell
 * P     - string representation for path
 *         0 :   uninitialized, for gaps at N- & C- termini when glocal>0
 *         1 : \ match-mismatch
 *         2 : | vertical gap (insertion)
 *         4 : - horizontal gap (deletion)
 * JumpH - horizontal long gap number.
 * JumpV - vertical long gap number.
 * all matrices are in the size of [len(seqx)+1]*[len(seqy)+1]
 *
 * glocal - global or local alignment
 *         0 : global alignment (Needleman-Wunsch dynamic programming)
 *         1 : glocal-query alignment
 *         2 : glocal-both alignment
 *         3 : local alignment (Smith-Waterman dynamic programming)
 *
 * alt_init - whether to adopt alternative matrix initialization
 *         1 : use wei zheng's matrix initialization
 *         0 : use yang zhang's matrix initialization, does NOT work
 *             for glocal alignment
 */
int calculate_score_gotoh(const int xlen,const int ylen, int **S,
    int** JumpH, int** JumpV, int **P, const int gapopen,const int gapext,
    const int glocal=0, const int alt_init=1)
{
    int **H;
    int **V;
    NewArray(&H,xlen+1,ylen+1); // penalty score for horizontal long gap
    NewArray(&V,xlen+1,ylen+1); // penalty score for vertical long gap
    
    // fill first row/colum of JumpH,jumpV and path matrix P
    int i,j;
    init_gotoh_mat(S, JumpH, JumpV, P, H, V, xlen, ylen,
        gapopen, gapext, glocal, alt_init);

    // fill S and P
    int diag_score,left_score,up_score;
    for (i=1;i<xlen+1;i++)
    {
        for (j=1;j<ylen+1;j++)
        {
            // penalty of consective deletion
            if (glocal<1 || i<xlen || glocal>=3)
            {
                H[i][j]=MAX(S[i][j-1]+gapopen,H[i][j-1]+gapext);
                JumpH[i][j]=(H[i][j]==H[i][j-1]+gapext)?(JumpH[i][j-1]+1):1;
            }
            else
            {
                H[i][j]=MAX(S[i][j-1],H[i][j-1]);
                JumpH[i][j]=(H[i][j]==H[i][j-1])?(JumpH[i][j-1]+1):1;
            }
            // penalty of consective insertion
            if (glocal<2 || j<ylen || glocal>=3)
            {
                V[i][j]=MAX(S[i-1][j]+gapopen,V[i-1][j]+gapext);
                JumpV[i][j]=(V[i][j]==V[i-1][j]+gapext)?(JumpV[i-1][j]+1):1;
            }
            else
            {
                V[i][j]=MAX(S[i-1][j],V[i-1][j]);
                JumpV[i][j]=(V[i][j]==V[i-1][j])?(JumpV[i-1][j]+1):1;
            }

            diag_score=S[i-1][j-1]+S[i][j]; // match-mismatch '\'
            left_score=H[i][j];     // deletion       '-'
            up_score  =V[i][j];     // insertion      '|'

            if (diag_score>=left_score && diag_score>=up_score)
            {
                S[i][j]=diag_score;
                P[i][j]+=1;
            }
            if (up_score>=diag_score && up_score>=left_score)
            {
                S[i][j]=up_score;
                P[i][j]+=2;
            }
            if (left_score>=diag_score && left_score>=up_score)
            {
                S[i][j]=left_score;
                P[i][j]+=4;
            }
            if (glocal>=3 && S[i][j]<0)
            {
                S[i][j]=0;
                P[i][j]=0;
                H[i][j]=0;
                V[i][j]=0;
                JumpH[i][j]=0;
                JumpV[i][j]=0;
            }
        }
    }
    int aln_score=S[xlen][ylen];

    // re-fill first row/column of path matrix P for back-tracing
    for (i=1;i<xlen+1;i++) if (glocal<3 || P[i][0]>0) P[i][0]=2; // |
    for (j=1;j<ylen+1;j++) if (glocal<3 || P[0][j]>0) P[0][j]=4; // -

    // calculate alignment score and alignment path for swalign
    if (glocal>=3)
        find_highest_align_score(S,P,aln_score,xlen,ylen);

    // release memory
    DeleteArray(&H,xlen+1);
    DeleteArray(&V,xlen+1);
    return aln_score; // final alignment score
}

/* trace back dynamic programming path to diciper pairwise alignment */
void trace_back_gotoh(const char *seqx, const char *seqy,
    int ** JumpH, int ** JumpV, int ** P, string& seqxA, string& seqyA,
    const int xlen, const int ylen, int *invmap, const int invmap_only=1)
{
    int i,j;
    int gaplen,p;
    char *buf=NULL;

    if (invmap_only) for (j = 0; j < ylen; j++) invmap[j] = -1;
    if (invmap_only!=1) buf=new char [MAX(xlen,ylen)+1];

    i=xlen;
    j=ylen;
    while(i+j)
    {
        gaplen=0;
        if (P[i][j]>=4)
        {
            gaplen=JumpH[i][j];
            j-=gaplen;
            if (invmap_only==1) continue;
            strncpy(buf,seqy+j,gaplen);
            buf[gaplen]=0;
            seqyA=buf+seqyA;

            for (p=0;p<gaplen;p++) buf[p]='-';
            seqxA=buf+seqxA;
        }
        else if (P[i][j] % 4 >= 2)
        {
            gaplen=JumpV[i][j];
            i-=gaplen;
            if (invmap_only==1) continue;
            strncpy(buf,seqx+i,gaplen);
            buf[gaplen]=0;
            seqxA=buf+seqxA;

            for (p=0;p<gaplen;p++) buf[p]='-';
            seqyA=buf+seqyA;
        }
        else
        {
            if (i==0 && j!=0) // only in glocal alignment
            {
                strncpy(buf,seqy,j);
                buf[j]=0;
                seqyA=buf+seqyA;
                for (p=0;p<j;p++) buf[p]='-';
                seqxA=buf+seqxA;
                break;
            }
            if (i!=0 && j==0) // only in glocal alignment
            {
                strncpy(buf,seqx,i);
                buf[i]=0;
                seqxA=buf+seqxA;
                for (p=0;p<i;p++) buf[p]='-';
                seqyA=buf+seqyA;
                break;
            }
            i--;
            j--;
            if (invmap_only) invmap[j]=i;
            if (invmap_only!=1)
            {
                seqxA=seqx[i]+seqxA;
                seqyA=seqy[j]+seqyA;
            }
        }
    }
    delete [] buf;
}


/* trace back Smith-Waterman dynamic programming path to diciper 
 * pairwise local alignment */
void trace_back_sw(const char *seqx, const char *seqy,
    int **JumpH, int **JumpV, int **P, string& seqxA, string& seqyA,
    const int xlen, const int ylen, int *invmap, const int invmap_only=1)
{
    int i;
    int j;
    int gaplen,p;
    bool found_start_cell=false; // find the first non-zero cell in P
    char *buf=NULL;

    if (invmap_only) for (j = 0; j < ylen; j++) invmap[j] = -1;
    if (invmap_only!=1) buf=new char [MAX(xlen,ylen)+1];

    i=xlen;
    j=ylen;
    for (i=xlen;i>=0;i--)
    {
        for (j=ylen;j>=0;j--)
        {
            if (P[i][j]!=0)
            {
                found_start_cell=true;
                break;
            }
        }
        if (found_start_cell) break;
    }

    /* copy C terminal sequence */
    if (invmap_only!=1)
    {
        for (p=0;p<ylen-j;p++) buf[p]='-';
        buf[ylen-j]=0;
        seqxA=buf;
        strncpy(buf,seqx+i,xlen-i);
        buf[xlen-i]=0;
        seqxA+=buf;

        strncpy(buf,seqy+j,ylen-j);
        buf[ylen-j]=0;
        seqyA+=buf;
        for (p=0;p<xlen-i;p++) buf[p]='-';
        buf[xlen-i]=0;
        seqyA+=buf;
    }

    if (i<0||j<0)
    {
        delete [] buf;
        return;
    }

    /* traceback aligned sequences */
    while(P[i][j]!=0)
    {
        gaplen=0;
        if (P[i][j]>=4)
        {
            gaplen=JumpH[i][j];
            j-=gaplen;
            if (invmap_only==1) continue;
            strncpy(buf,seqy+j,gaplen);
            buf[gaplen]=0;
            seqyA=buf+seqyA;

            for (p=0;p<gaplen;p++) buf[p]='-';
            seqxA=buf+seqxA;
        }
        else if (P[i][j] % 4 >= 2)
        {
            gaplen=JumpV[i][j];
            i-=gaplen;
            if (invmap_only==1) continue;
            strncpy(buf,seqx+i,gaplen);
            buf[gaplen]=0;
            seqxA=buf+seqxA;

            for (p=0;p<gaplen;p++) buf[p]='-';
            seqyA=buf+seqyA;
        }
        else
        {
            i--;
            j--;
            if (invmap_only) invmap[j]=i;
            if (invmap_only!=1)
            {
                seqxA=seqx[i]+seqxA;
                seqyA=seqy[j]+seqyA;
            }
        }
    }
    /* copy N terminal sequence */
    if (invmap_only!=1)
    {
        for (p=0;p<j;p++) buf[p]='-';
        strncpy(buf+j,seqx,i);
        buf[i+j]=0;
        seqxA=buf+seqxA;

        strncpy(buf,seqy,j);
        for (p=j;p<j+i;p++) buf[p]='-';
        buf[i+j]=0;
        seqyA=buf+seqyA;
    }
    delete [] buf;
}

/* entry function for NWalign
 * invmap_only - whether to return seqxA and seqyA or to return invmap
 *               0: only return seqxA and seqyA
 *               1: only return invmap
 *               2: return seqxA, seqyA and invmap */
int NWalign_main(const char *seqx, const char *seqy, const int xlen,
    const int ylen, string & seqxA, string & seqyA, const int mol_type,
    int *invmap, const int invmap_only=0, const int glocal=0)
{
    int **JumpH;
    int **JumpV;
    int **P;
    int **S;
    NewArray(&JumpH,xlen+1,ylen+1);
    NewArray(&JumpV,xlen+1,ylen+1);
    NewArray(&P,xlen+1,ylen+1);
    NewArray(&S,xlen+1,ylen+1);
    
    int aln_score;
    int gapopen=gapopen_blosum62;
    int gapext =gapext_blosum62;
    int i,j;
    if (mol_type>0) // RNA or DNA
    {
        gapopen=gapopen_blastn;
        gapext =gapext_blastn;
        if (glocal==3)
        {
            gapopen=-5;
            gapext =-2;
        }
    }

    for (i=0;i<xlen+1;i++)
    {
        for (j=0;j<ylen+1;j++)
        {
            if (i*j==0) S[i][j]=0;
            else S[i][j]=BLOSUM[seqx[i-1]][seqy[j-1]];
        }
    }

    aln_score=calculate_score_gotoh(xlen, ylen, S, JumpH, JumpV, P,
        gapopen, gapext, glocal);

    seqxA.clear();
    seqyA.clear();

    if (glocal<3) trace_back_gotoh(seqx, seqy, JumpH, JumpV, P,
            seqxA, seqyA, xlen, ylen, invmap, invmap_only);
    else trace_back_sw(seqx, seqy, JumpH, JumpV, P, seqxA, seqyA,
            xlen, ylen, invmap, invmap_only);

    DeleteArray(&JumpH, xlen+1);
    DeleteArray(&JumpV, xlen+1);
    DeleteArray(&P, xlen+1);
    DeleteArray(&S, xlen+1);
    return aln_score; // aligment score
}

void get_seqID(int *invmap, const char *seqx, const char *seqy, 
    const int ylen, double &Liden,int &L_ali)
{
    Liden=0;
    L_ali=0;
    int i,j;
    for (j=0;j<ylen;j++)
    {
        i=invmap[j];
        if (i<0) continue;
        L_ali+=1;
        Liden+=(seqx[i]==seqy[j]);
    }
    //return L_ali?1.*Liden/L_ali:0;
}

void get_seqID(const string& seqxA, const string& seqyA,
    string &seqM,double &Liden,int &L_ali)
{
    Liden=0;
    L_ali=0;
    for (int i=0;i<seqxA.size();i++)
    {
        if (seqxA[i]==seqyA[i] && seqxA[i]!='-')
        {
            Liden++;
            seqM+=':';
        }
        else seqM+=' ';
        L_ali+=(seqxA[i]!='-' && seqyA[i]!='-');
    }
    //return L_ali?1.*Liden/L_ali:0;
}

void output_NWalign_results(
    const string xname, const string yname,
    const char *chainID1, const char *chainID2,
    const int xlen, const int ylen, const char *seqM, 
    const char *seqxA, const char *seqyA, const double Liden,
    const int L_ali, const int aln_score, const int outfmt_opt)
{
    if (outfmt_opt<=0)
    {
        printf("\nName of Chain_1: %s%s\n", xname.c_str(), chainID1);
        printf("Name of Chain_2: %s%s\n", yname.c_str(), chainID2);
        printf("Length of Chain_1: %d residues\n", xlen);
        printf("Length of Chain_2: %d residues\n\n", ylen);

        printf("Aligned length= %d, Alignment score= %d, Seq_ID=n_identical/n_aligned= %4.3f\n",
            L_ali, aln_score, Liden/L_ali);
        printf("Seq_ID= %6.5f (if normalized by length of Chain_1\n", Liden/xlen);
        printf("Seq_ID= %6.5f (if normalized by length of Chain_2\n", Liden/ylen);
        printf("(You should use Seq_ID normalized by length of the reference structure)\n");
    
        //output alignment
        printf("\n(\":\" denotes pairs with identical residue type)\n");
        printf("%s\n", seqxA);
        printf("%s\n", seqM);
        printf("%s\n", seqyA);
    }
    else if (outfmt_opt==1)
    {
        printf(">%s%s\tL=%d\tseqID=%.3f\n",
            xname.c_str(), chainID1, xlen, Liden/xlen);
        printf("%s\n", seqxA);
        printf(">%s%s\tL=%d\tseqID=%.3f\n",
            yname.c_str(), chainID2, ylen, Liden/ylen);
        printf("%s\n", seqyA);
        printf("#score=%d\tLali=%d\tseqID_ali=%.3f\n", aln_score, L_ali, Liden/L_ali);
        printf("$$$$\n");
    }
    else if (outfmt_opt==2)
    {
        printf("%s%s\t%s%s\t%4.3f\t%4.3f\t%4.3f\t%d\t%d\t%d",
            xname.c_str(), chainID1, yname.c_str(), chainID2, 
            Liden/xlen, Liden/ylen, Liden/L_ali,
            xlen, ylen, L_ali);
    }
    cout << endl;
}

/* extract pairwise sequence alignment from residue index vectors,
 * assuming that "sequence" contains two empty strings.
 * return length of alignment, including gap. */
int extract_aln_from_resi(vector<string> &sequence, char *seqx, char *seqy,
    const vector<string> resi_vec1, const vector<string> resi_vec2,
    const int byresi_opt)
{
    sequence.clear();
    sequence.push_back("");
    sequence.push_back("");

    int i1=0; // positions in resi_vec1
    int i2=0; // positions in resi_vec2
    int xlen=resi_vec1.size();
    int ylen=resi_vec2.size();
    if (byresi_opt==4 || byresi_opt==5 || byresi_opt==7) // global or glocal sequence alignment
    {
        int *invmap;
        int glocal=0;
        if (byresi_opt==5 || byresi_opt==7) glocal=2;
        int mol_type=0;
        for (i1=0;i1<xlen;i1++)
            if ('a'<seqx[i1] && seqx[i1]<'z') mol_type++;
            else mol_type--;
        for (i2=0;i2<ylen;i2++)
            if ('a'<seqy[i2] && seqy[i2]<'z') mol_type++;
            else mol_type--;
        NWalign_main(seqx, seqy, xlen, ylen, sequence[0],sequence[1],
            mol_type, invmap, 0, glocal);
    }


    map<string,string> chainID_map1;
    map<string,string> chainID_map2;
    if (byresi_opt==3)
    {
        vector<string> chainID_vec;
        string chainID;
        stringstream ss;
        int i;
        for (i=0;i<xlen;i++)
        {
            chainID=resi_vec1[i].substr(5);
            if (!chainID_vec.size()|| chainID_vec.back()!=chainID)
            {
                chainID_vec.push_back(chainID);
                ss<<chainID_vec.size();
                chainID_map1[chainID]=ss.str();
                ss.str("");
            }
        }
        chainID_vec.clear();
        for (i=0;i<ylen;i++)
        {
            chainID=resi_vec2[i].substr(5);
            if (!chainID_vec.size()|| chainID_vec.back()!=chainID)
            {
                chainID_vec.push_back(chainID);
                ss<<chainID_vec.size();
                chainID_map2[chainID]=ss.str();
                ss.str("");
            }
        }
        vector<string>().swap(chainID_vec);
    }
    string chainID1="";
    string chainID2="";
    string chainID1_prev="";
    string chainID2_prev="";
    while(i1<xlen && i2<ylen)
    {
        if (byresi_opt==2)
        {
            chainID1=resi_vec1[i1].substr(5);
            chainID2=resi_vec2[i2].substr(5);
        }
        else if (byresi_opt==3)
        {
            chainID1=chainID_map1[resi_vec1[i1].substr(5)];
            chainID2=chainID_map2[resi_vec2[i2].substr(5)];
        }

        if (chainID1==chainID2)
        {
            if (atoi(resi_vec1[i1].substr(0,4).c_str())<
                atoi(resi_vec2[i2].substr(0,4).c_str()))
            {
                sequence[0]+=seqx[i1++];
                sequence[1]+='-';
            }
            else if (atoi(resi_vec1[i1].substr(0,4).c_str())>
                     atoi(resi_vec2[i2].substr(0,4).c_str()))
            {
                sequence[0]+='-';
                sequence[1]+=seqy[i2++];
            }
            else
            {
                sequence[0]+=seqx[i1++];
                sequence[1]+=seqy[i2++];
            }
            chainID1_prev=chainID1;
            chainID2_prev=chainID2;
        }
        else
        {
            if (chainID1_prev==chainID1 && chainID2_prev!=chainID2)
            {
                sequence[0]+=seqx[i1++];
                sequence[1]+='-';
                chainID1_prev=chainID1;
            }
            else if (chainID1_prev!=chainID1 && chainID2_prev==chainID2)
            {
                sequence[0]+='-';
                sequence[1]+=seqy[i2++];
                chainID2_prev=chainID2;
            }
            else
            {
                sequence[0]+=seqx[i1++];
                sequence[1]+=seqy[i2++];
                chainID1_prev=chainID1;
                chainID2_prev=chainID2;
            }
        }
        
    }
    map<string,string>().swap(chainID_map1);
    map<string,string>().swap(chainID_map2);
    chainID1.clear();
    chainID2.clear();
    chainID1_prev.clear();
    chainID2_prev.clear();
    return sequence[0].size();
}

/* extract pairwise sequence alignment from residue index vectors,
 * return length of alignment, including gap. */
int extract_aln_from_resi(vector<string> &sequence, char *seqx, char *seqy,
    const vector<string> resi_vec1, const vector<string> resi_vec2,
    const vector<int> xlen_vec, const vector<int> ylen_vec,
    const int chain_i, const int chain_j, const int byresi_opt)
{
    sequence.clear();
    sequence.push_back("");
    sequence.push_back("");

    int i1=0; // positions in resi_vec1
    int i2=0; // positions in resi_vec2
    int xlen=xlen_vec[chain_i];
    int ylen=ylen_vec[chain_j];
    int i,j;
    for (i=0;i<chain_i;i++) i1+=xlen_vec[i];
    for (j=0;j<chain_j;j++) i2+=ylen_vec[j];

    if (byresi_opt==7)
    {
        int *invmap;
        int glocal=2;
        int mol_type=0;

        for (i=0;i<xlen;i++)
            if ('a'<seqx[i] && seqx[i]<'z') mol_type++;
            else mol_type--;
        for (i=0;i<ylen;i++)
            if ('a'<seqy[i] && seqy[i]<'z') mol_type++;
            else mol_type--;
        NWalign_main(seqx, seqy, xlen, ylen, sequence[0],sequence[1],
            mol_type, invmap, 0, glocal);
        delete [] invmap;
        return sequence[0].size();
    }

    i=j=0;
    while(i<xlen && j<ylen)
    {
        if (atoi(resi_vec1[i+i1].substr(0,4).c_str())<
            atoi(resi_vec2[j+i2].substr(0,4).c_str()))
        {
            sequence[0]+=seqx[i++];
            sequence[1]+='-';
        }
        else if (atoi(resi_vec1[i+i1].substr(0,4).c_str())>
                 atoi(resi_vec2[j+i2].substr(0,4).c_str()))
        {
            sequence[0]+='-';
            sequence[1]+=seqy[j++];
        }
        else
        {
            sequence[0]+=seqx[i++];
            sequence[1]+=seqy[j++];
        }
    }
    if (i<xlen && j==ylen)
    {
        for (;i<xlen;i++)
        {
            sequence[0]+=seqx[i];
            sequence[1]+='-';
        }
    }
    else if (i==xlen && j<ylen)
    {
        for (;j<ylen;j++)
        {
            sequence[0]+='-';
            sequence[1]+=seqy[j];
        }
    }
    return sequence[0].size();
}

#endif

// ===== END NWalign.h =====

//     1, collect those residues with dis<d;
//     2, calculate TMscore
int score_fun8(double **xa, double **ya, int n_ali, double d, int i_ali[],
               double *score1, int score_sum_method, const double Lnorm,
               const double score_d8, const double d0)
{
    double score_sum = 0, di;
    double d_tmp = d * d;
    double d02 = d0 * d0;
    double score_d8_cut = score_d8 * score_d8;

    int i, n_cut, inc = 0;

    while (1)
    {
        n_cut = 0;
        score_sum = 0;
        for (i = 0; i < n_ali; i++)
        {
            di = dist(xa[i], ya[i]);
            if (di < d_tmp)
            {
                i_ali[n_cut] = i;
                n_cut++;
            }
            if (score_sum_method == 8)
            {
                if (di <= score_d8_cut)
                    score_sum += 1 / (1 + di / d02);
            }
            else
                score_sum += 1 / (1 + di / d02);
        }
        // there are not enough feasible pairs, relieve the threshold
        if (n_cut < 3 && n_ali > 3)
        {
            inc++;
            double dinc = (d + inc * 0.5);
            d_tmp = dinc * dinc;
        }
        else
            break;
    }

    *score1 = score_sum / Lnorm;
    return n_cut;
}

int score_fun8_standard(double **xa, double **ya, int n_ali, double d,
                        int i_ali[], double *score1, int score_sum_method,
                        double score_d8, double d0)
{
    double score_sum = 0, di;
    double d_tmp = d * d;
    double d02 = d0 * d0;
    double score_d8_cut = score_d8 * score_d8;

    int i, n_cut, inc = 0;
    while (1)
    {
        n_cut = 0;
        score_sum = 0;
        for (i = 0; i < n_ali; i++)
        {
            di = dist(xa[i], ya[i]);
            if (di < d_tmp)
            {
                i_ali[n_cut] = i;
                n_cut++;
            }
            if (score_sum_method == 8)
            {
                if (di <= score_d8_cut)
                    score_sum += 1 / (1 + di / d02);
            }
            else
            {
                score_sum += 1 / (1 + di / d02);
            }
        }
        // there are not enough feasible pairs, relieve the threshold
        if (n_cut < 3 && n_ali > 3)
        {
            inc++;
            double dinc = (d + inc * 0.5);
            d_tmp = dinc * dinc;
        }
        else
            break;
    }

    *score1 = score_sum / n_ali;
    return n_cut;
}

double TMscore8_search(double **r1, double **r2, double **xtm, double **ytm,
                       double **xt, int Lali, double t0[3], double u0[3][3], int simplify_step,
                       int score_sum_method, double *Rcomm, double local_d0_search, double Lnorm,
                       double score_d8, double d0)
{
    int i, m;
    double score_max, score, rmsd;
    const int kmax = Lali;
    int k_ali[kmax], ka, k;
    double t[3];
    double u[3][3];
    double d;

    // iterative parameters
    int n_it = 20;         // maximum number of iterations
    int n_init_max = 6;    // maximum number of different fragment length
    int L_ini[n_init_max]; // fragment lengths, Lali, Lali/2, Lali/4 ... 4
    int L_ini_min = 4;
    if (Lali < L_ini_min)
        L_ini_min = Lali;

    int n_init = 0, i_init;
    for (i = 0; i < n_init_max - 1; i++)
    {
        n_init++;
        L_ini[i] = (int)(Lali / pow(2.0, (double)i));
        if (L_ini[i] <= L_ini_min)
        {
            L_ini[i] = L_ini_min;
            break;
        }
    }
    if (i == n_init_max - 1)
    {
        n_init++;
        L_ini[i] = L_ini_min;
    }

    score_max = -1;
    // find the maximum score starting from local structures superposition
    int i_ali[kmax], n_cut;
    int L_frag; // fragment length
    int iL_max; // maximum starting position for the fragment

    for (i_init = 0; i_init < n_init; i_init++)
    {
        L_frag = L_ini[i_init];
        iL_max = Lali - L_frag;

        i = 0;
        while (1)
        {
            // extract the fragment starting from position i
            ka = 0;
            for (k = 0; k < L_frag; k++)
            {
                int kk = k + i;
                r1[k][0] = xtm[kk][0];
                r1[k][1] = xtm[kk][1];
                r1[k][2] = xtm[kk][2];

                r2[k][0] = ytm[kk][0];
                r2[k][1] = ytm[kk][1];
                r2[k][2] = ytm[kk][2];

                k_ali[ka] = kk;
                ka++;
            }

            // extract rotation matrix based on the fragment
            Kabsch(r1, r2, L_frag, 1, &rmsd, t, u);
            if (simplify_step != 1)
                *Rcomm = 0;
            do_rotation(xtm, xt, Lali, t, u);

            // get subsegment of this fragment
            d = local_d0_search - 1;
            n_cut = score_fun8(xt, ytm, Lali, d, i_ali, &score,
                               score_sum_method, Lnorm, score_d8, d0);
            if (score > score_max)
            {
                score_max = score;

                // save the rotation matrix
                for (k = 0; k < 3; k++)
                {
                    t0[k] = t[k];
                    u0[k][0] = u[k][0];
                    u0[k][1] = u[k][1];
                    u0[k][2] = u[k][2];
                }
            }

            // try to extend the alignment iteratively
            d = local_d0_search + 1;
            for (int it = 0; it < n_it; it++)
            {
                ka = 0;
                for (k = 0; k < n_cut; k++)
                {
                    m = i_ali[k];
                    r1[k][0] = xtm[m][0];
                    r1[k][1] = xtm[m][1];
                    r1[k][2] = xtm[m][2];

                    r2[k][0] = ytm[m][0];
                    r2[k][1] = ytm[m][1];
                    r2[k][2] = ytm[m][2];

                    k_ali[ka] = m;
                    ka++;
                }
                // extract rotation matrix based on the fragment
                Kabsch(r1, r2, n_cut, 1, &rmsd, t, u);
                do_rotation(xtm, xt, Lali, t, u);
                n_cut = score_fun8(xt, ytm, Lali, d, i_ali, &score,
                                   score_sum_method, Lnorm, score_d8, d0);
                if (score > score_max)
                {
                    score_max = score;

                    // save the rotation matrix
                    for (k = 0; k < 3; k++)
                    {
                        t0[k] = t[k];
                        u0[k][0] = u[k][0];
                        u0[k][1] = u[k][1];
                        u0[k][2] = u[k][2];
                    }
                }

                // check if it converges
                if (n_cut == ka)
                {
                    for (k = 0; k < n_cut; k++)
                    {
                        if (i_ali[k] != k_ali[k])
                            break;
                    }
                    if (k == n_cut)
                        break;
                }
            } // for iteration

            if (i < iL_max)
            {
                i = i + simplify_step; // shift the fragment
                if (i > iL_max)
                    i = iL_max; // do this to use the last missed fragment
            }
            else if (i >= iL_max)
                break;
        } // while(1)
        // end of one fragment
    } // for(i_init
    return score_max;
}

double TMscore8_search_standard(double **r1, double **r2,
                                double **xtm, double **ytm, double **xt, int Lali,
                                double t0[3], double u0[3][3], int simplify_step, int score_sum_method,
                                double *Rcomm, double local_d0_search, double score_d8, double d0)
{
    int i, m;
    double score_max, score, rmsd;
    const int kmax = Lali;
    int k_ali[kmax], ka, k;
    double t[3];
    double u[3][3];
    double d;

    // iterative parameters
    int n_it = 20;         // maximum number of iterations
    int n_init_max = 6;    // maximum number of different fragment length
    int L_ini[n_init_max]; // fragment lengths, Lali, Lali/2, Lali/4 ... 4
    int L_ini_min = 4;
    if (Lali < L_ini_min)
        L_ini_min = Lali;

    int n_init = 0, i_init;
    for (i = 0; i < n_init_max - 1; i++)
    {
        n_init++;
        L_ini[i] = (int)(Lali / pow(2.0, (double)i));
        if (L_ini[i] <= L_ini_min)
        {
            L_ini[i] = L_ini_min;
            break;
        }
    }
    if (i == n_init_max - 1)
    {
        n_init++;
        L_ini[i] = L_ini_min;
    }

    score_max = -1;
    // find the maximum score starting from local structures superposition
    int i_ali[kmax], n_cut;
    int L_frag; // fragment length
    int iL_max; // maximum starting position for the fragment

    for (i_init = 0; i_init < n_init; i_init++)
    {
        L_frag = L_ini[i_init];
        iL_max = Lali - L_frag;

        i = 0;
        while (1)
        {
            // extract the fragment starting from position i
            ka = 0;
            for (k = 0; k < L_frag; k++)
            {
                int kk = k + i;
                r1[k][0] = xtm[kk][0];
                r1[k][1] = xtm[kk][1];
                r1[k][2] = xtm[kk][2];

                r2[k][0] = ytm[kk][0];
                r2[k][1] = ytm[kk][1];
                r2[k][2] = ytm[kk][2];

                k_ali[ka] = kk;
                ka++;
            }
            // extract rotation matrix based on the fragment
            Kabsch(r1, r2, L_frag, 1, &rmsd, t, u);
            if (simplify_step != 1)
                *Rcomm = 0;
            do_rotation(xtm, xt, Lali, t, u);

            // get subsegment of this fragment
            d = local_d0_search - 1;
            n_cut = score_fun8_standard(xt, ytm, Lali, d, i_ali, &score,
                                        score_sum_method, score_d8, d0);

            if (score > score_max)
            {
                score_max = score;

                // save the rotation matrix
                for (k = 0; k < 3; k++)
                {
                    t0[k] = t[k];
                    u0[k][0] = u[k][0];
                    u0[k][1] = u[k][1];
                    u0[k][2] = u[k][2];
                }
            }

            // try to extend the alignment iteratively
            d = local_d0_search + 1;
            for (int it = 0; it < n_it; it++)
            {
                ka = 0;
                for (k = 0; k < n_cut; k++)
                {
                    m = i_ali[k];
                    r1[k][0] = xtm[m][0];
                    r1[k][1] = xtm[m][1];
                    r1[k][2] = xtm[m][2];

                    r2[k][0] = ytm[m][0];
                    r2[k][1] = ytm[m][1];
                    r2[k][2] = ytm[m][2];

                    k_ali[ka] = m;
                    ka++;
                }
                // extract rotation matrix based on the fragment
                Kabsch(r1, r2, n_cut, 1, &rmsd, t, u);
                do_rotation(xtm, xt, Lali, t, u);
                n_cut = score_fun8_standard(xt, ytm, Lali, d, i_ali, &score,
                                            score_sum_method, score_d8, d0);
                if (score > score_max)
                {
                    score_max = score;

                    // save the rotation matrix
                    for (k = 0; k < 3; k++)
                    {
                        t0[k] = t[k];
                        u0[k][0] = u[k][0];
                        u0[k][1] = u[k][1];
                        u0[k][2] = u[k][2];
                    }
                }

                // check if it converges
                if (n_cut == ka)
                {
                    for (k = 0; k < n_cut; k++)
                    {
                        if (i_ali[k] != k_ali[k])
                            break;
                    }
                    if (k == n_cut)
                        break;
                }
            } // for iteration

            if (i < iL_max)
            {
                i = i + simplify_step; // shift the fragment
                if (i > iL_max)
                    i = iL_max; // do this to use the last missed fragment
            }
            else if (i >= iL_max)
                break;
        } // while(1)
        // end of one fragment
    } // for(i_init
    return score_max;
}

// Comprehensive TMscore search engine
//  input:   two vector sets: x, y
//           an alignment invmap0[] between x and y
//           simplify_step: 1 or 40 or other integers
//           score_sum_method: 0 for score over all pairs
//                             8 for socre over the pairs with dist<score_d8
//  output:  the best rotaion matrix t, u that results in highest TMscore
double detailed_search(double **r1, double **r2, double **xtm, double **ytm,
                       double **xt, double **x, double **y, int xlen, int ylen,
                       int invmap0[], double t[3], double u[3][3], int simplify_step,
                       int score_sum_method, double local_d0_search, double Lnorm,
                       double score_d8, double d0)
{
    // x is model, y is template, try to superpose onto y
    int i, j, k;
    double tmscore;
    double rmsd;

    k = 0;
    for (i = 0; i < ylen; i++)
    {
        j = invmap0[i];
        if (j >= 0) // aligned
        {
            xtm[k][0] = x[j][0];
            xtm[k][1] = x[j][1];
            xtm[k][2] = x[j][2];

            ytm[k][0] = y[i][0];
            ytm[k][1] = y[i][1];
            ytm[k][2] = y[i][2];
            k++;
        }
    }

    // detailed search 40-->1
    tmscore = TMscore8_search(r1, r2, xtm, ytm, xt, k, t, u, simplify_step,
                              score_sum_method, &rmsd, local_d0_search, Lnorm, score_d8, d0);
    return tmscore;
}

double detailed_search_standard(double **r1, double **r2,
                                double **xtm, double **ytm, double **xt, double **x, double **y,
                                int xlen, int ylen, int invmap0[], double t[3], double u[3][3],
                                int simplify_step, int score_sum_method, double local_d0_search,
                                const bool &bNormalize, double Lnorm, double score_d8, double d0)
{
    // x is model, y is template, try to superpose onto y
    int i, j, k;
    double tmscore;
    double rmsd;

    k = 0;
    for (i = 0; i < ylen; i++)
    {
        j = invmap0[i];
        if (j >= 0) // aligned
        {
            xtm[k][0] = x[j][0];
            xtm[k][1] = x[j][1];
            xtm[k][2] = x[j][2];

            ytm[k][0] = y[i][0];
            ytm[k][1] = y[i][1];
            ytm[k][2] = y[i][2];
            k++;
        }
    }

    // detailed search 40-->1
    tmscore = TMscore8_search_standard(r1, r2, xtm, ytm, xt, k, t, u,
                                       simplify_step, score_sum_method, &rmsd, local_d0_search, score_d8, d0);
    if (bNormalize) // "-i", to use standard_TMscore, then bNormalize=true, else bNormalize=false;
        tmscore = tmscore * k / Lnorm;

    return tmscore;
}

// compute the score quickly in three iterations
double get_score_fast(double **r1, double **r2, double **xtm, double **ytm,
                      double **x, double **y, int xlen, int ylen, int invmap[],
                      double d0, double d0_search, double t[3], double u[3][3])
{
    double rms, tmscore, tmscore1, tmscore2;
    int i, j, k;

    k = 0;
    for (j = 0; j < ylen; j++)
    {
        i = invmap[j];
        if (i >= 0)
        {
            r1[k][0] = x[i][0];
            r1[k][1] = x[i][1];
            r1[k][2] = x[i][2];

            r2[k][0] = y[j][0];
            r2[k][1] = y[j][1];
            r2[k][2] = y[j][2];

            xtm[k][0] = x[i][0];
            xtm[k][1] = x[i][1];
            xtm[k][2] = x[i][2];

            ytm[k][0] = y[j][0];
            ytm[k][1] = y[j][1];
            ytm[k][2] = y[j][2];

            k++;
        }
        else if (i != -1)
            PrintErrorAndQuit("Wrong map!\n");
    }
    Kabsch(r1, r2, k, 1, &rms, t, u);

    // evaluate score
    double di;
    const int len = k;
    double dis[len];
    double d00 = d0_search;
    double d002 = d00 * d00;
    double d02 = d0 * d0;

    int n_ali = k;
    double xrot[3];
    tmscore = 0;
    for (k = 0; k < n_ali; k++)
    {
        transform(t, u, &xtm[k][0], xrot);
        di = dist(xrot, &ytm[k][0]);
        dis[k] = di;
        tmscore += 1 / (1 + di / d02);
    }

    // second iteration
    double d002t = d002;
    vector<double> dis_vec(dis, dis + n_ali);
    sort(dis_vec.begin(), dis_vec.end());
    if (dis_vec.size()>2)
    {
        if (d002t < dis_vec[2])
            d002t = dis_vec[2];
    }
    else if (d002t < dis_vec.back())
        d002t = dis_vec.back();
    dis_vec.clear();
    while (1)
    {
        j = 0;
        for (k = 0; k < n_ali; k++)
        {
            if (dis[k] <= d002t)
            {
                r1[j][0] = xtm[k][0];
                r1[j][1] = xtm[k][1];
                r1[j][2] = xtm[k][2];

                r2[j][0] = ytm[k][0];
                r2[j][1] = ytm[k][1];
                r2[j][2] = ytm[k][2];

                j++;
            }
        }
        // there are not enough feasible pairs, relieve the threshold
        if (j < 3 && n_ali > 3)
            d002t += 0.5;
        else
            break;
    }

    if (n_ali != j)
    {
        Kabsch(r1, r2, j, 1, &rms, t, u);
        tmscore1 = 0;
        for (k = 0; k < n_ali; k++)
        {
            transform(t, u, &xtm[k][0], xrot);
            di = dist(xrot, &ytm[k][0]);
            dis[k] = di;
            tmscore1 += 1 / (1 + di / d02);
        }

        // third iteration
        d002t = d002 + 1;
        vector<double> dis_vec(dis, dis + n_ali);
        sort(dis_vec.begin(), dis_vec.end());
        if (dis_vec.size()>2)
        {
            if (d002t < dis_vec[2])
                d002t = dis_vec[2];
        }
        else if (d002t < dis_vec.back())
            d002t = dis_vec.back();

        dis_vec.clear();
        while (1)
        {
            j = 0;
            for (k = 0; k < n_ali; k++)
            {
                if (dis[k] <= d002t)
                {
                    r1[j][0] = xtm[k][0];
                    r1[j][1] = xtm[k][1];
                    r1[j][2] = xtm[k][2];

                    r2[j][0] = ytm[k][0];
                    r2[j][1] = ytm[k][1];
                    r2[j][2] = ytm[k][2];

                    j++;
                }
            }
            // there are not enough feasible pairs, relieve the threshold
            if (j < 3 && n_ali > 3)
                d002t += 0.5;
            else
                break;
        }

        // evaluate the score
        Kabsch(r1, r2, j, 1, &rms, t, u);
        tmscore2 = 0;
        for (k = 0; k < n_ali; k++)
        {
            transform(t, u, &xtm[k][0], xrot);
            di = dist(xrot, &ytm[k][0]);
            tmscore2 += 1 / (1 + di / d02);
        }
    }
    else
    {
        tmscore1 = tmscore;
        tmscore2 = tmscore;
    }

    if (tmscore1 >= tmscore)
        tmscore = tmscore1;
    if (tmscore2 >= tmscore)
        tmscore = tmscore2;
    return tmscore; // no need to normalize this score because it will not be used for latter scoring
}

// perform gapless threading to find the best initial alignment
// input: x, y, xlen, ylen
// output: y2x0 stores the best alignment: e.g.,
// y2x0[j]=i means:
// the jth element in y is aligned to the ith element in x if i>=0
// the jth element in y is aligned to a gap in x if i==-1
double get_initial(double **r1, double **r2, double **xtm, double **ytm,
                   double **x, double **y, int xlen, int ylen, int *y2x,
                   double d0, double d0_search, const bool fast_opt,
                   double t[3], double u[3][3])
{
    int min_len = getmin(xlen, ylen);
    if (min_len < 3)
        PrintErrorAndQuit("Sequence is too short <3!\n");

    int min_ali = min_len / 2; // minimum size of considered fragment
    if (min_ali <= 5)
        min_ali = 5;
    int n1, n2;
    n1 = -ylen + min_ali;
    n2 = xlen - min_ali;

    int i, j, k, k_best;
    double tmscore, tmscore_max = -1;

    k_best = n1;
    for (k = n1; k <= n2; k += (fast_opt) ? 5 : 1)
    {
        // get the map
        for (j = 0; j < ylen; j++)
        {
            i = j + k;
            if (i >= 0 && i < xlen)
                y2x[j] = i;
            else
                y2x[j] = -1;
        }

        // evaluate the map quickly in three iterations
        // this is not real tmscore, it is used to evaluate the goodness of the initial alignment
        tmscore = get_score_fast(r1, r2, xtm, ytm,
                                 x, y, xlen, ylen, y2x, d0, d0_search, t, u);
        if (tmscore >= tmscore_max)
        {
            tmscore_max = tmscore;
            k_best = k;
        }
    }

    // extract the best map
    k = k_best;
    for (j = 0; j < ylen; j++)
    {
        i = j + k;
        if (i >= 0 && i < xlen)
            y2x[j] = i;
        else
            y2x[j] = -1;
    }

    return tmscore_max;
}

void smooth(int *sec, int len)
{
    int i, j;
    // smooth single  --x-- => -----
    for (i = 2; i < len - 2; i++)
    {
        if (sec[i] == 2 || sec[i] == 4)
        {
            j = sec[i];
            if (sec[i - 2] != j && sec[i - 1] != j && sec[i + 1] != j && sec[i + 2] != j)
                sec[i] = 1;
        }
    }

    //   smooth double
    //   --xx-- => ------
    for (i = 0; i < len - 5; i++)
    {
        // helix
        if (sec[i] != 2 && sec[i + 1] != 2 && sec[i + 2] == 2 && sec[i + 3] == 2 &&
            sec[i + 4] != 2 && sec[i + 5] != 2)
        {
            sec[i + 2] = 1;
            sec[i + 3] = 1;
        }

        // beta
        if (sec[i] != 4 && sec[i + 1] != 4 && sec[i + 2] == 4 && sec[i + 3] == 4 &&
            sec[i + 4] != 4 && sec[i + 5] != 4)
        {
            sec[i + 2] = 1;
            sec[i + 3] = 1;
        }
    }

    // smooth connect
    for (i = 0; i < len - 2; i++)
    {
        if (sec[i] == 2 && sec[i + 1] != 2 && sec[i + 2] == 2)
            sec[i + 1] = 2;
        else if (sec[i] == 4 && sec[i + 1] != 4 && sec[i + 2] == 4)
            sec[i + 1] = 4;
    }
}

char sec_str(double dis13, double dis14, double dis15,
             double dis24, double dis25, double dis35)
{
    char s = 'C';

    double delta = 2.1;
    if (fabs(dis15 - 6.37) < delta && fabs(dis14 - 5.18) < delta &&
        fabs(dis25 - 5.18) < delta && fabs(dis13 - 5.45) < delta &&
        fabs(dis24 - 5.45) < delta && fabs(dis35 - 5.45) < delta)
    {
        s = 'H'; // helix
        return s;
    }

    delta = 1.42;
    if (fabs(dis15 - 13) < delta && fabs(dis14 - 10.4) < delta &&
        fabs(dis25 - 10.4) < delta && fabs(dis13 - 6.1) < delta &&
        fabs(dis24 - 6.1) < delta && fabs(dis35 - 6.1) < delta)
    {
        s = 'E'; // strand
        return s;
    }

    if (dis15 < 8)
        s = 'T'; // turn
    return s;
}

/* secondary structure assignment for protein:
 * 1->coil, 2->helix, 3->turn, 4->strand */
void make_sec(double **x, int len, char *sec)
{
    int j1, j2, j3, j4, j5;
    double d13, d14, d15, d24, d25, d35;
    for (int i = 0; i < len; i++)
    {
        sec[i] = 'C';
        j1 = i - 2;
        j2 = i - 1;
        j3 = i;
        j4 = i + 1;
        j5 = i + 2;

        if (j1 >= 0 && j5 < len)
        {
            d13 = sqrt(dist(x[j1], x[j3]));
            d14 = sqrt(dist(x[j1], x[j4]));
            d15 = sqrt(dist(x[j1], x[j5]));
            d24 = sqrt(dist(x[j2], x[j4]));
            d25 = sqrt(dist(x[j2], x[j5]));
            d35 = sqrt(dist(x[j3], x[j5]));
            sec[i] = sec_str(d13, d14, d15, d24, d25, d35);
        }
    }
    sec[len] = 0;
}

/* a c d b: a paired to b, c paired to d */
bool overlap(const int a1, const int b1, const int c1, const int d1,
             const int a2, const int b2, const int c2, const int d2)
{
    return (a2 >= a1 && a2 <= c1) || (c2 >= a1 && c2 <= c1) ||
           (d2 >= a1 && d2 <= c1) || (b2 >= a1 && b2 <= c1) ||
           (a2 >= d1 && a2 <= b1) || (c2 >= d1 && c2 <= b1) ||
           (d2 >= d1 && d2 <= b1) || (b2 >= d1 && b2 <= b1);
}

/* find base pairing stacks in RNA*/
void sec_str(int len, char *seq, const vector<vector<bool> > &bp,
             int a, int b, int &c, int &d)
{
    int i;

    for (i = 0; i < len; i++)
    {
        if (a + i < len - 3 && b - i > 0)
        {
            if (a + i < b - i && bp[a + i][b - i])
                continue;
            break;
        }
    }
    c = a + i - 1;
    d = b - i + 1;
}

/* secondary structure assignment for RNA:
 * 1->unpair, 2->paired with upstream, 3->paired with downstream */
void make_sec(char *seq, double **x, int len, char *sec, const string atom_opt)
{
    int ii, jj, i, j;

    float lb = 12.5; // lower bound for " C3'"
    float ub = 15.0; // upper bound for " C3'"
    if (atom_opt == " C4'")
    {
        lb = 14.0;
        ub = 16.0;
    }
    else if (atom_opt == " C5'")
    {
        lb = 16.0;
        ub = 18.0;
    }
    else if (atom_opt == " O3'")
    {
        lb = 13.5;
        ub = 16.5;
    }
    else if (atom_opt == " O5'")
    {
        lb = 15.5;
        ub = 18.5;
    }
    else if (atom_opt == " P  ")
    {
        lb = 16.5;
        ub = 21.0;
    }

    float dis;
    vector<bool> bp_tmp(len, false);
    vector<vector<bool> > bp(len, bp_tmp);
    bp_tmp.clear();
    for (i = 0; i < len; i++)
    {
        sec[i] = '.';
        for (j = i + 1; j < len; j++)
        {
            if (((seq[i] == 'u' || seq[i] == 't') && (seq[j] == 'a')) ||
                ((seq[i] == 'a') && (seq[j] == 'u' || seq[j] == 't')) ||
                ((seq[i] == 'g') && (seq[j] == 'c' || seq[j] == 'u')) ||
                ((seq[i] == 'c' || seq[i] == 'u') && (seq[j] == 'g')))
            {
                dis = sqrt(dist(x[i], x[j]));
                bp[j][i] = bp[i][j] = (dis > lb && dis < ub);
            }
        }
    }

    // From 5' to 3': A0_var C0_var D0_var B0_var: A0_var paired to B0_var, C0_var paired to D0_var
    vector<int> A0_var, B0_var, C0_var, D0_var;
    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 3; j < len; j++)
        {
            if (!bp[i][j])
                continue;
            if (i > 0 && j + 1 < len && bp[i - 1][j + 1])
                continue;
            if (!bp[i + 1][j - 1])
                continue;
            sec_str(len, seq, bp, i, j, ii, jj);
            if (jj < i || j < ii)
            {
                ii = i;
                jj = j;
            }
            A0_var.push_back(i);
            B0_var.push_back(j);
            C0_var.push_back(ii);
            D0_var.push_back(jj);
        }
    }

    // int sign;
    for (i = 0; i < A0_var.size(); i++)
    {
        /*
        sign=0;
        if(C0_var[i]-A0_var[i]<=1)
        {
            for(j=0;j<A0_var.size();j++)
            {
                if(i==j) continue;

                if((A0_var[j]>=A0_var[i]&&A0_var[j]<=C0_var[i])||
                   (C0_var[j]>=A0_var[i]&&C0_var[j]<=C0_var[i])||
                   (D0_var[j]>=A0_var[i]&&D0_var[j]<=C0_var[i])||
                   (B0_var[j]>=A0_var[i]&&B0_var[j]<=C0_var[i])||
                   (A0_var[j]>=D0_var[i]&&A0_var[j]<=B0_var[i])||
                   (C0_var[j]>=D0_var[i]&&C0_var[j]<=B0_var[i])||
                   (D0_var[j]>=D0_var[i]&&D0_var[j]<=B0_var[i])||
                   (B0_var[j]>=D0_var[i]&&B0_var[j]<=B0_var[i]))
                {
                    sign=-1;
                    break;
                }
            }
        }
        if(sign!=0) continue;
        */

        for (j = 0;; j++)
        {
            if (A0_var[i] + j > C0_var[i])
                break;
            sec[A0_var[i] + j] = '<';
            sec[D0_var[i] + j] = '>';
        }
    }
    sec[len] = 0;

    /* clean up */
    A0_var.clear();
    B0_var.clear();
    C0_var.clear();
    D0_var.clear();
    bp.clear();
}

// get initial alignment from secondary structure alignment
// input: x, y, xlen, ylen
// output: y2x stores the best alignment: e.g.,
// y2x[j]=i means:
// the jth element in y is aligned to the ith element in x if i>=0
// the jth element in y is aligned to a gap in x if i==-1
void get_initial_ss(bool **path, double **val,
                    const char *secx, const char *secy, int xlen, int ylen, int *y2x)
{
    double gap_open = -1.0;
    NWDP_TM(path, val, secx, secy, xlen, ylen, gap_open, y2x);
}

// get_initial5 in TMalign fortran, get_initial_local in TMalign c by yangji
// get initial alignment of local structure superposition
// input: x, y, xlen, ylen
// output: y2x stores the best alignment: e.g.,
// y2x[j]=i means:
// the jth element in y is aligned to the ith element in x if i>=0
// the jth element in y is aligned to a gap in x if i==-1
bool get_initial5(double **r1, double **r2, double **xtm, double **ytm,
                  bool **path, double **val,
                  double **x, double **y, int xlen, int ylen, int *y2x,
                  double d0, double d0_search, const bool fast_opt, const double D0_MIN)
{
    double GL, rmsd;
    double t[3];
    double u[3][3];

    double d01 = d0 + 1.5;
    if (d01 < D0_MIN)
        d01 = D0_MIN;
    double d02 = d01 * d01;

    double GLmax = 0;
    int aL = getmin(xlen, ylen);
    int *invmap = new int[ylen + 1];

    // jump on sequence1-------------->
    int n_jump1 = 0;
    if (xlen > 250)
        n_jump1 = 45;
    else if (xlen > 200)
        n_jump1 = 35;
    else if (xlen > 150)
        n_jump1 = 25;
    else
        n_jump1 = 15;
    if (n_jump1 > (xlen / 3))
        n_jump1 = xlen / 3;

    // jump on sequence2-------------->
    int n_jump2 = 0;
    if (ylen > 250)
        n_jump2 = 45;
    else if (ylen > 200)
        n_jump2 = 35;
    else if (ylen > 150)
        n_jump2 = 25;
    else
        n_jump2 = 15;
    if (n_jump2 > (ylen / 3))
        n_jump2 = ylen / 3;

    // fragment to superimpose-------------->
    int n_frag[2] = {20, 100};
    if (n_frag[0] > (aL / 3))
        n_frag[0] = aL / 3;
    if (n_frag[1] > (aL / 2))
        n_frag[1] = aL / 2;

    // start superimpose search-------------->
    if (fast_opt)
    {
        n_jump1 *= 5;
        n_jump2 *= 5;
    }
    bool flag = false;
    for (int i_frag = 0; i_frag < 2; i_frag++)
    {
        int m1 = xlen - n_frag[i_frag] + 1;
        int m2 = ylen - n_frag[i_frag] + 1;

        for (int i = 0; i < m1; i = i + n_jump1) // index starts from 0, different from FORTRAN
        {
            for (int j = 0; j < m2; j = j + n_jump2)
            {
                for (int k = 0; k < n_frag[i_frag]; k++) // fragment in y
                {
                    r1[k][0] = x[k + i][0];
                    r1[k][1] = x[k + i][1];
                    r1[k][2] = x[k + i][2];

                    r2[k][0] = y[k + j][0];
                    r2[k][1] = y[k + j][1];
                    r2[k][2] = y[k + j][2];
                }

                // superpose the two structures and rotate it
                Kabsch(r1, r2, n_frag[i_frag], 1, &rmsd, t, u);

                double gap_open = 0.0;
                NWDP_TM(path, val, x, y, xlen, ylen,
                        t, u, d02, gap_open, invmap);
                GL = get_score_fast(r1, r2, xtm, ytm, x, y, xlen, ylen,
                                    invmap, d0, d0_search, t, u);
                if (GL > GLmax)
                {
                    GLmax = GL;
                    for (int ii = 0; ii < ylen; ii++)
                        y2x[ii] = invmap[ii];
                    flag = true;
                }
            }
        }
    }

    delete[] invmap;
    return flag;
}

void score_matrix_rmsd_sec(double **r1, double **r2, double **score,
                           const char *secx, const char *secy, double **x, double **y,
                           int xlen, int ylen, int *y2x, const double D0_MIN, double d0)
{
    double t[3], u[3][3];
    double rmsd, dij;
    double d01 = d0 + 1.5;
    if (d01 < D0_MIN)
        d01 = D0_MIN;
    double d02 = d01 * d01;

    double xx[3];
    int i, k = 0;
    for (int j = 0; j < ylen; j++)
    {
        i = y2x[j];
        if (i >= 0)
        {
            r1[k][0] = x[i][0];
            r1[k][1] = x[i][1];
            r1[k][2] = x[i][2];

            r2[k][0] = y[j][0];
            r2[k][1] = y[j][1];
            r2[k][2] = y[j][2];

            k++;
        }
    }
    Kabsch(r1, r2, k, 1, &rmsd, t, u);

    for (int ii = 0; ii < xlen; ii++)
    {
        transform(t, u, &x[ii][0], xx);
        for (int jj = 0; jj < ylen; jj++)
        {
            dij = dist(xx, &y[jj][0]);
            if (secx[ii] == secy[jj])
                score[ii + 1][jj + 1] = 1.0 / (1 + dij / d02) + 0.5;
            else
                score[ii + 1][jj + 1] = 1.0 / (1 + dij / d02);
        }
    }
}

// get initial alignment from secondary structure and previous alignments
// input: x, y, xlen, ylen
// output: y2x stores the best alignment: e.g.,
// y2x[j]=i means:
// the jth element in y is aligned to the ith element in x if i>=0
// the jth element in y is aligned to a gap in x if i==-1
void get_initial_ssplus(double **r1, double **r2, double **score, bool **path,
                        double **val, const char *secx, const char *secy, double **x, double **y,
                        int xlen, int ylen, int *y2x0, int *y2x, const double D0_MIN, double d0)
{
    // create score matrix for DP
    score_matrix_rmsd_sec(r1, r2, score, secx, secy, x, y, xlen, ylen,
                          y2x0, D0_MIN, d0);

    double gap_open = -1.0;
    NWDP_TM(score, path, val, xlen, ylen, gap_open, y2x);
}

void find_max_frag(double **x, int len, int *start_max,
                   int *end_max, double dcu0, const bool fast_opt)
{
    int r_min, fra_min = 4; // minimum fragment for search
    if (fast_opt)
        fra_min = 8;
    int start;
    int Lfr_max = 0;

    r_min = (int)(len * 1.0 / 3.0); // minimum fragment, in case too small protein
    if (r_min > fra_min)
        r_min = fra_min;

    int inc = 0;
    double dcu0_cut = dcu0 * dcu0;
    ;
    double dcu_cut = dcu0_cut;

    while (Lfr_max < r_min)
    {
        Lfr_max = 0;
        int j = 1; // number of residues at nf-fragment
        start = 0;
        for (int i = 1; i < len; i++)
        {
            if (dist(x[i - 1], x[i]) < dcu_cut)
            {
                j++;

                if (i == (len - 1))
                {
                    if (j > Lfr_max)
                    {
                        Lfr_max = j;
                        *start_max = start;
                        *end_max = i;
                    }
                    j = 1;
                }
            }
            else
            {
                if (j > Lfr_max)
                {
                    Lfr_max = j;
                    *start_max = start;
                    *end_max = i - 1;
                }

                j = 1;
                start = i;
            }
        } // for i;

        if (Lfr_max < r_min)
        {
            inc++;
            double dinc = pow(1.1, (double)inc) * dcu0;
            dcu_cut = dinc * dinc;
        }
    } // while <;
}

// perform fragment gapless threading to find the best initial alignment
// input: x, y, xlen, ylen
// output: y2x0 stores the best alignment: e.g.,
// y2x0[j]=i means:
// the jth element in y is aligned to the ith element in x if i>=0
// the jth element in y is aligned to a gap in x if i==-1
double get_initial_fgt(double **r1, double **r2, double **xtm, double **ytm,
                       double **x, double **y, int xlen, int ylen,
                       int *y2x, double d0, double d0_search,
                       double dcu0, const bool fast_opt, double t[3], double u[3][3])
{
    int fra_min = 4; // minimum fragment for search
    if (fast_opt)
        fra_min = 8;
    int fra_min1 = fra_min - 1; // cutoff for shift, save time

    int xstart = 0, ystart = 0, xend = 0, yend = 0;

    find_max_frag(x, xlen, &xstart, &xend, dcu0, fast_opt);
    find_max_frag(y, ylen, &ystart, &yend, dcu0, fast_opt);

    int Lx = xend - xstart + 1;
    int Ly = yend - ystart + 1;
    int *ifr, *y2x_;
    int L_fr = getmin(Lx, Ly);
    ifr = new int[L_fr];
    y2x_ = new int[ylen + 1];

    // select what piece will be used. The original implement may cause
    // asymetry, but only when xlen==ylen and Lx==Ly
    // if L1=Lfr1 and L2=Lfr2 (normal proteins), it will be the same as initial1

    if (Lx < Ly || (Lx == Ly && xlen < ylen))
    {
        for (int i = 0; i < L_fr; i++)
            ifr[i] = xstart + i;
    }
    else if (Lx > Ly || (Lx == Ly && xlen > ylen))
    {
        for (int i = 0; i < L_fr; i++)
            ifr[i] = ystart + i;
    }
    else // solve asymetric for 1x5gA vs 2q7nA5
    {
        /* In this case, L0==xlen==ylen; L_fr==Lx==Ly */
        int L0 = xlen;
        double tmscore, tmscore_max = -1;
        int i, j, k;
        int n1, n2;
        int min_len;
        int min_ali;

        /* part 1, normalized by xlen */
        for (i = 0; i < L_fr; i++)
            ifr[i] = xstart + i;

        if (L_fr == L0)
        {
            n1 = (int)(L0 * 0.1); // my index starts from 0
            n2 = (int)(L0 * 0.89);
            j = 0;
            for (i = n1; i <= n2; i++)
            {
                ifr[j] = ifr[i];
                j++;
            }
            L_fr = j;
        }

        int L1 = L_fr;
        min_len = getmin(L1, ylen);
        min_ali = (int)(min_len / 2.5); // minimum size of considered fragment
        if (min_ali <= fra_min1)
            min_ali = fra_min1;
        n1 = -ylen + min_ali;
        n2 = L1 - min_ali;

        for (k = n1; k <= n2; k += (fast_opt) ? 3 : 1)
        {
            // get the map
            for (j = 0; j < ylen; j++)
            {
                i = j + k;
                if (i >= 0 && i < L1)
                    y2x_[j] = ifr[i];
                else
                    y2x_[j] = -1;
            }

            // evaluate the map quickly in three iterations
            tmscore = get_score_fast(r1, r2, xtm, ytm, x, y, xlen, ylen, y2x_,
                                     d0, d0_search, t, u);

            if (tmscore >= tmscore_max)
            {
                tmscore_max = tmscore;
                for (j = 0; j < ylen; j++)
                    y2x[j] = y2x_[j];
            }
        }

        /* part 2, normalized by ylen */
        L_fr = Ly;
        for (i = 0; i < L_fr; i++)
            ifr[i] = ystart + i;

        if (L_fr == L0)
        {
            n1 = (int)(L0 * 0.1); // my index starts from 0
            n2 = (int)(L0 * 0.89);

            j = 0;
            for (i = n1; i <= n2; i++)
            {
                ifr[j] = ifr[i];
                j++;
            }
            L_fr = j;
        }

        int L2 = L_fr;
        min_len = getmin(xlen, L2);
        min_ali = (int)(min_len / 2.5); // minimum size of considered fragment
        if (min_ali <= fra_min1)
            min_ali = fra_min1;
        n1 = -L2 + min_ali;
        n2 = xlen - min_ali;

        for (k = n1; k <= n2; k++)
        {
            // get the map
            for (j = 0; j < ylen; j++)
                y2x_[j] = -1;

            for (j = 0; j < L2; j++)
            {
                i = j + k;
                if (i >= 0 && i < xlen)
                    y2x_[ifr[j]] = i;
            }

            // evaluate the map quickly in three iterations
            tmscore = get_score_fast(r1, r2, xtm, ytm,
                                     x, y, xlen, ylen, y2x_, d0, d0_search, t, u);
            if (tmscore >= tmscore_max)
            {
                tmscore_max = tmscore;
                for (j = 0; j < ylen; j++)
                    y2x[j] = y2x_[j];
            }
        }

        delete[] ifr;
        delete[] y2x_;
        return tmscore_max;
    }

    int L0 = getmin(xlen, ylen); // non-redundant to get_initial1
    if (L_fr == L0)
    {
        int n1 = (int)(L0 * 0.1); // my index starts from 0
        int n2 = (int)(L0 * 0.89);

        int j = 0;
        for (int i = n1; i <= n2; i++)
        {
            ifr[j] = ifr[i];
            j++;
        }
        L_fr = j;
    }

    // gapless threading for the extracted fragment
    double tmscore, tmscore_max = -1;

    if (Lx < Ly || (Lx == Ly && xlen <= ylen))
    {
        int L1 = L_fr;
        int min_len = getmin(L1, ylen);
        int min_ali = (int)(min_len / 2.5); // minimum size of considered fragment
        if (min_ali <= fra_min1)
            min_ali = fra_min1;
        int n1, n2;
        n1 = -ylen + min_ali;
        n2 = L1 - min_ali;

        int i, j, k;
        for (k = n1; k <= n2; k += (fast_opt) ? 3 : 1)
        {
            // get the map
            for (j = 0; j < ylen; j++)
            {
                i = j + k;
                if (i >= 0 && i < L1)
                    y2x_[j] = ifr[i];
                else
                    y2x_[j] = -1;
            }

            // evaluate the map quickly in three iterations
            tmscore = get_score_fast(r1, r2, xtm, ytm, x, y, xlen, ylen, y2x_,
                                     d0, d0_search, t, u);

            if (tmscore >= tmscore_max)
            {
                tmscore_max = tmscore;
                for (j = 0; j < ylen; j++)
                    y2x[j] = y2x_[j];
            }
        }
    }
    else
    {
        int L2 = L_fr;
        int min_len = getmin(xlen, L2);
        int min_ali = (int)(min_len / 2.5); // minimum size of considered fragment
        if (min_ali <= fra_min1)
            min_ali = fra_min1;
        int n1, n2;
        n1 = -L2 + min_ali;
        n2 = xlen - min_ali;

        int i, j, k;

        for (k = n1; k <= n2; k++)
        {
            // get the map
            for (j = 0; j < ylen; j++)
                y2x_[j] = -1;

            for (j = 0; j < L2; j++)
            {
                i = j + k;
                if (i >= 0 && i < xlen)
                    y2x_[ifr[j]] = i;
            }

            // evaluate the map quickly in three iterations
            tmscore = get_score_fast(r1, r2, xtm, ytm,
                                     x, y, xlen, ylen, y2x_, d0, d0_search, t, u);
            if (tmscore >= tmscore_max)
            {
                tmscore_max = tmscore;
                for (j = 0; j < ylen; j++)
                    y2x[j] = y2x_[j];
            }
        }
    }

    delete[] ifr;
    delete[] y2x_;
    return tmscore_max;
}

// heuristic run of dynamic programing iteratively to find the best alignment
// input: initial rotation matrix t, u
//        vectors x and y, d0
// output: best alignment that maximizes the TMscore, will be stored in invmap
double DP_iter(double **r1, double **r2, double **xtm, double **ytm,
               double **xt, bool **path, double **val, double **x, double **y,
               int xlen, int ylen, double t[3], double u[3][3], int invmap0[],
               int g1, int g2, int iteration_max, double local_d0_search,
               double D0_MIN, double Lnorm, double d0, double score_d8)
{
    double gap_open[2] = {-0.6, 0};
    double rmsd;
    int *invmap = new int[ylen + 1];

    int iteration, i, j, k;
    double tmscore, tmscore_max, tmscore_old = 0;
    int score_sum_method = 8, simplify_step = 40;
    tmscore_max = -1;

    // double d01=d0+1.5;
    double d02 = d0 * d0;
    for (int g = g1; g < g2; g++)
    {
        for (iteration = 0; iteration < iteration_max; iteration++)
        {
            NWDP_TM(path, val, x, y, xlen, ylen,
                    t, u, d02, gap_open[g], invmap);

            k = 0;
            for (j = 0; j < ylen; j++)
            {
                i = invmap[j];

                if (i >= 0) // aligned
                {
                    xtm[k][0] = x[i][0];
                    xtm[k][1] = x[i][1];
                    xtm[k][2] = x[i][2];

                    ytm[k][0] = y[j][0];
                    ytm[k][1] = y[j][1];
                    ytm[k][2] = y[j][2];
                    k++;
                }
            }

            tmscore = TMscore8_search(r1, r2, xtm, ytm, xt, k, t, u,
                                      simplify_step, score_sum_method, &rmsd, local_d0_search,
                                      Lnorm, score_d8, d0);

            if (tmscore > tmscore_max)
            {
                tmscore_max = tmscore;
                for (i = 0; i < ylen; i++)
                    invmap0[i] = invmap[i];
            }

            if (iteration > 0)
            {
                if (fabs(tmscore_old - tmscore) < 0.000001)
                    break;
            }
            tmscore_old = tmscore;
        } // for iteration

    } // for gapopen

    delete[] invmap;
    return tmscore_max;
}

/* script format: 0 - no script; 1 - pymol; 3 - chimerax */
void output_pymol(const string xname, const string yname,
                  const string fname_super, double t[3], double u[3][3], const int ter_opt,
                  const int mm_opt, const int split_opt, const int mirror_opt,
                  const char *seqM, const char *seqxA, const char *seqyA,
                  const vector<string> &resi_vec1, const vector<string> &resi_vec2,
                  const string chainID1, const string chainID2, const int o_opt = 1)
{
    int compress_type = 0; // uncompressed file
    ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
    ifstream fin_gz;
#else
    redi::ipstream fin_gz; // if file is compressed
    if (xname.size() >= 3 &&
        xname.substr(xname.size() - 3, 3) == ".gz")
    {
        fin_gz.open("gunzip -c " + xname);
        compress_type = 1;
    }
    else if (xname.size() >= 4 &&
             xname.substr(xname.size() - 4, 4) == ".bz2")
    {
        fin_gz.open("bzcat " + xname);
        compress_type = 2;
    }
    else
#endif
    fin.open(xname.c_str());

    stringstream buf;
    stringstream buf_pymol;
    string line;
    double x[3];  // before transform
    double x1[3]; // after transform

    /* for PDBx/mmCIF only */
    map<string, int> _atom_site;
    size_t atom_site_pos;
    vector<string> line_vec;
    int infmt = -1; // 0 - PDB, 3 - PDBx/mmCIF

    while (compress_type ? fin_gz.good() : fin.good())
    {
        if (compress_type)
            getline(fin_gz, line);
        else
            getline(fin, line);
        if (line.compare(0, 6, "ATOM  ") == 0 ||
            line.compare(0, 6, "HETATM") == 0) // PDB format
        {
            infmt = 0;
            x[0] = atof(line.substr(30, 8).c_str());
            x[1] = atof(line.substr(38, 8).c_str());
            x[2] = atof(line.substr(46, 8).c_str());
            if (mirror_opt)
                x[2] = -x[2];
            transform(t, u, x, x1);
            buf << line.substr(0, 30) << setiosflags(ios::fixed)
                << setprecision(3)
                << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2]
                << line.substr(54) << '\n';
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            infmt = 3;
            buf << line << '\n';
            while (1)
            {
                if (compress_type)
                {
                    if (fin_gz.good())
                        getline(fin_gz, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                else
                {
                    if (fin.good())
                        getline(fin, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                if (line.size())
                    break;
            }
            buf << line << '\n';
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[Trim(line.substr(11))] = atom_site_pos;
            while (1)
            {
                while (1)
                {
                    if (compress_type)
                    {
                        if (fin_gz.good())
                            getline(fin_gz, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    else
                    {
                        if (fin.good())
                            getline(fin, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    if (line.size())
                        break;
                }
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[Trim(line.substr(11))] = ++atom_site_pos;
                buf << line << '\n';
            }

            if (_atom_site.count("group_PDB") *
                    _atom_site.count("Cartn_x") *
                    _atom_site.count("Cartn_y") *
                    _atom_site.count("Cartn_z") ==
                0)
            {
                buf << line << '\n';
                cerr << "Warning! Missing one of the following _atom_site data items: group_PDB, Cartn_x, Cartn_y, Cartn_z" << endl;
                continue;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;

                x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                if (mirror_opt)
                    x[2] = -x[2];
                transform(t, u, x, x1);

                for (atom_site_pos = 0; atom_site_pos < _atom_site.size(); atom_site_pos++)
                {
                    if (atom_site_pos == _atom_site["Cartn_x"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[0] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_y"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[1] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_z"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[2] << ' ';
                    else
                        buf << line_vec[atom_site_pos] << ' ';
                }
                buf << '\n';

                if (compress_type && fin_gz.good())
                    getline(fin_gz, line);
                else if (!compress_type && fin.good())
                    getline(fin, line);
                else
                    break;
            }
            if (compress_type ? fin_gz.good() : fin.good())
                buf << line << '\n';
        }
        else if (line.size())
        {
            buf << line << '\n';
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    if (compress_type)
        fin_gz.close();
    else
        fin.close();

    string fname_super_full = fname_super;
    if (infmt == 0)
        fname_super_full += ".pdb";
    else if (infmt == 3)
        fname_super_full += ".cif";
    ofstream fp;
    fp.open(fname_super_full.c_str());
    fp << buf.str();
    fp.close();
    buf.str(string()); // clear stream

    string chain1_sele;
    string chain2_sele;
    int i;
    if (!mm_opt)
    {
        if (split_opt == 2 && ter_opt >= 1) // align one chain from model 1
        {
            if (o_opt == 1)
            {
                chain1_sele = " and c. " + chainID1.substr(1);
                chain2_sele = " and c. " + chainID2.substr(1);
            }
            else if (o_opt == 3)
            {
                chain1_sele = "/" + chainID1.substr(1);
                chain2_sele = "/" + chainID2.substr(1);
            }
        }
        else if (split_opt == 2 && ter_opt == 0) // align one chain from each model
        {
            for (i = 1; i < chainID1.size(); i++)
                if (chainID1[i] == ',')
                    break;
            if (o_opt == 1)
                chain1_sele = " and c. " + chainID1.substr(i + 1);
            else if (o_opt == 3)
                chain1_sele = "/" + chainID1.substr(i + 1);
            for (i = 1; i < chainID2.size(); i++)
                if (chainID2[i] == ',')
                    break;
            if (o_opt == 1)
                chain2_sele = " and c. " + chainID2.substr(i + 1);
            else if (o_opt == 3)
                chain2_sele = "/" + chainID2.substr(i + 1);
        }
    }

    /* extract aligned region */
    int i1 = -1;
    int i2 = -1;
    string resi1_sele;
    string resi2_sele;
    string resi1_bond;
    string resi2_bond;
    string prev_resi1;
    string prev_resi2;
    string curr_resi1;
    string curr_resi2;
    if (mm_opt)
    {
        ;
    }
    else
    {
        for (i = 0; i < strlen(seqM); i++)
        {
            i1 += (seqxA[i] != '-' && seqxA[i] != '*');
            i2 += (seqyA[i] != '-');
            if (seqM[i] == ' ' || seqxA[i] == '*')
                continue;
            curr_resi1 = resi_vec1[i1].substr(0, 4);
            curr_resi2 = resi_vec2[i2].substr(0, 4);
            if (resi1_sele.size() == 0)
            {
                if (o_opt == 1)
                    resi1_sele = "i. " + curr_resi1;
                else if (o_opt == 3)
                    resi1_sele = ":" + Trim(curr_resi1);
            }
            else
            {
                if (o_opt == 1)
                {
                    resi1_sele += " or i. " + curr_resi1;
                    resi1_bond += "bond structure1 and i. " + prev_resi1 +
                                  ", structure1 and i. " + curr_resi1 + "\n";
                }
                else if (o_opt == 3)
                    resi1_sele += "," + Trim(curr_resi1);
            }
            if (resi2_sele.size() == 0)
            {
                if (o_opt == 1)
                    resi2_sele = "i. " + curr_resi2;
                else if (o_opt == 3)
                    resi2_sele = ":" + Trim(curr_resi2);
            }
            else
            {
                if (o_opt == 1)
                {
                    resi2_sele += " or i. " + curr_resi2;
                    resi2_bond += "bond structure2 and i. " + prev_resi2 +
                                  ", structure2 and i. " + curr_resi2 + "\n";
                }
                else if (o_opt == 3)
                    resi2_sele += "," + Trim(curr_resi2);
            }
            prev_resi1 = curr_resi1;
            prev_resi2 = curr_resi2;
            // if (seqM[i]!=':') continue;
        }
        if (o_opt == 1)
        {
            if (resi1_sele.size())
                resi1_sele = " and ( " + resi1_sele + ")";
            if (resi2_sele.size())
                resi2_sele = " and ( " + resi2_sele + ")";
        }
    }

    /* write pymol script */
    vector<string> pml_list;
    pml_list.push_back(fname_super + "");
    pml_list.push_back(fname_super + "_atm");
    pml_list.push_back(fname_super + "_all");
    pml_list.push_back(fname_super + "_all_atm");
    pml_list.push_back(fname_super + "_all_atm_lig");

    for (int p = 0; p < pml_list.size(); p++)
    {
        if (mm_opt && p <= 1)
            continue;
        if (o_opt == 1)
            buf_pymol
                << "#!/usr/bin/env pymol\n"
                << "cmd.load(\"" << fname_super_full << "\", \"structure1\")\n"
                << "cmd.load(\"" << yname << "\", \"structure2\")\n"
                << "hide all\n"
                << "set all_states, " << ((ter_opt == 0) ? "on" : "off") << '\n';
        else if (o_opt == 3)
            buf_pymol
                << "#!/usr/bin/env chimerax --script\n"
                << "open \"" << fname_super_full << "\" name structure1\n"
                << "open \"" << yname << "\" name structure2\n";
        if (p == 0) // .pml
        {
            if (o_opt == 1)
            {
                if (chain1_sele.size())
                    buf_pymol
                        << "remove structure1 and not " << chain1_sele.substr(4) << "\n";
                if (chain2_sele.size())
                    buf_pymol
                        << "remove structure2 and not " << chain2_sele.substr(4) << "\n";
                buf_pymol
                    << "remove not n. CA and not n. C3'\n"
                    << resi1_bond
                    << resi2_bond
                    << "show stick, structure1" << chain1_sele << resi1_sele << "\n"
                    << "show stick, structure2" << chain2_sele << resi2_sele << "\n";
            }
            else if (o_opt == 3)
            {
                buf_pymol
                    << "hide all cartoon\n"
                    << "hide all atoms\n"
                    << "hide all bonds\n"
                    << "show #1" << chain1_sele << resi1_sele << " cartoon\n"
                    << "show #2" << chain2_sele << resi2_sele << " cartoon\n"
                    << "cartoon style protein modeh default arrows f xsect oval width 1 thick 1\n"
                    << "cartoon style nucleic xsect oval width 1 thick 1\n";
            }
        }
        else if (p == 1) // _atm.pml
        {
            if (o_opt == 1)
                buf_pymol
                    << "show cartoon, structure1" << chain1_sele << resi1_sele << "\n"
                    << "show cartoon, structure2" << chain2_sele << resi2_sele << "\n";
            else if (o_opt == 3)
                buf_pymol
                    << "hide all cartoon\n"
                    << "hide all atoms\n"
                    << "hide all bonds\n"
                    << "show #1" << chain1_sele << resi1_sele << " & protein cartoon\n"
                    << "show #1" << chain1_sele << resi1_sele << " & nucleic cartoon,bonds\n"
                    << "show #2" << chain2_sele << resi2_sele << " & protein cartoon\n"
                    << "show #2" << chain2_sele << resi2_sele << " & nucleic cartoon,bonds\n";
        }
        else if (p == 2) // _all.pml
        {
            if (o_opt == 1)
                buf_pymol
                    << "show ribbon, structure1" << chain1_sele << "\n"
                    << "show ribbon, structure2" << chain2_sele << "\n";
            else if (o_opt == 3)
                buf_pymol
                    << "hide all atoms\n"
                    << "hide all bonds\n"
                    << "cartoon style protein modeh default arrows f xsect oval width 1 thick 1\n"
                    << "cartoon style nucleic xsect oval width 1 thick 1\n"
                    << "hide all cartoon\n"
                    << "show #1" << chain1_sele << " cartoon\n"
                    << "show #2" << chain2_sele << " cartoon\n";
        }
        else if (p == 3) // _all_atm.pml
        {
            if (o_opt == 1)
                buf_pymol
                    << "show cartoon, structure1" << chain1_sele << "\n"
                    << "show cartoon, structure2" << chain2_sele << "\n";
            else if (o_opt == 3)
                buf_pymol
                    << "hide all bonds\n"
                    << "hide all atoms\n"
                    << "hide all cartoon\n"
                    << "show #1" << chain1_sele << " cartoon\n"
                    << "show #1" << chain1_sele << " & nucleic cartoon,bonds\n"
                    << "show #2" << chain2_sele << " cartoon\n"
                    << "show #2" << chain2_sele << " & nucleic cartoon,bonds\n";
        }
        else if (p == 4) // _all_atm_lig.pml
        {
            if (o_opt == 1)
                buf_pymol
                    << "show cartoon, structure1\n"
                    << "show cartoon, structure2\n"
                    << "show stick, not polymer\n"
                    << "show sphere, not polymer\n";
            else if (o_opt == 3)
                buf_pymol
                    << "show solvent atoms,bonds\n";
        }
        if (o_opt == 1)
            buf_pymol
                << "color blue, structure1\n"
                << "color red, structure2\n"
                << "set ribbon_width, 6\n"
                << "set stick_radius, 0.3\n"
                << "set sphere_scale, 0.25\n"
                << "set ray_shadow, 0\n"
                << "bg_color white\n"
                << "set transparency=0.2\n"
                << "zoom polymer and ((structure1" << chain1_sele
                << ") or (structure2" << chain2_sele << "))\n"
                << endl;
        else if (o_opt == 3)
            buf_pymol
                << "color #1 blue\n"
                << "color #2 red\n"
                << "set bgColor white\n"
                << endl;

        if (o_opt == 1) // pymol
            fp.open((pml_list[p] + ".pml").c_str());
        else if (o_opt == 3) // chimerax
            fp.open((pml_list[p] + ".cxc").c_str());
        fp << buf_pymol.str();
        fp.close();
        buf_pymol.str(string());
    }

    /* clean up */
    pml_list.clear();

    resi1_sele.clear();
    resi2_sele.clear();

    resi1_bond.clear();
    resi2_bond.clear();

    prev_resi1.clear();
    prev_resi2.clear();

    curr_resi1.clear();
    curr_resi2.clear();

    chain1_sele.clear();
    chain2_sele.clear();
}

void output_mTMalign_pymol(const vector<string> &chain_list,
                           const int infmt_opt, double **ut_mat, const string &fname_super,
                           const int o_opt = 1)
{
    int compress_type = 0; // uncompressed file
    size_t m;
    string name;
    double t[3];
    double u[3][3];
    int ui, uj;
    string filename;
    vector<string> color_list;
    color_list.push_back("red");
    color_list.push_back("green");
    color_list.push_back("blue");
    color_list.push_back("yellow");
    color_list.push_back("violet");
    color_list.push_back("cyan");
    color_list.push_back("salmon");
    color_list.push_back("lime");
    color_list.push_back("pink");
    color_list.push_back("slate");
    color_list.push_back("magenta");
    color_list.push_back("orange");
    color_list.push_back("marine");
    color_list.push_back("olive");
    color_list.push_back("purple");
    color_list.push_back("teal");
    color_list.push_back("forest");
    color_list.push_back("firebrick");
    color_list.push_back("chocolate");
    color_list.push_back("wheat");
    color_list.push_back("white");
    color_list.push_back("grey");

    stringstream buf_pymol;
    if (o_opt == 1)
        buf_pymol << "#!/usr/bin/env pymol\n";
    else if (o_opt == 3)
        buf_pymol << "#!/usr/bin/env chimerax --script\n";
    for (m = 0; m < chain_list.size(); m++)
    {
        name = chain_list[m];

        ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
        ifstream fin_gz;
#else
        redi::ipstream fin_gz; // if file is compressed
        if (name.size() >= 3 &&
            name.substr(name.size() - 3, 3) == ".gz")
        {
            fin_gz.open("gunzip -c " + name);
            compress_type = 1;
        }
        else if (name.size() >= 4 &&
                 name.substr(name.size() - 4, 4) == ".bz2")
        {
            fin_gz.open("bzcat " + name);
            compress_type = 2;
        }
        else
#endif
        fin.open(name.c_str());

        stringstream buf;
        buf << fname_super << '.' << m << ".pdb";
        filename = buf.str();
        buf.str(string());
        for (ui = 0; ui < 3; ui++)
            for (uj = 0; uj < 3; uj++)
                u[ui][uj] = ut_mat[m][ui * 3 + uj];
        for (uj = 0; uj < 3; uj++)
            t[uj] = ut_mat[m][9 + uj];

        string line;
        double x[3];  // before transform
        double x1[3]; // after transform

        /* for PDBx/mmCIF only */
        map<string, int> _atom_site;
        size_t atom_site_pos;
        vector<string> line_vec;
        int infmt = -1; // 0 - PDB, 3 - PDBx/mmCIF

        while (compress_type ? fin_gz.good() : fin.good())
        {
            if (compress_type)
                getline(fin_gz, line);
            else
                getline(fin, line);
            if (line.compare(0, 6, "ATOM  ") == 0 ||
                line.compare(0, 6, "HETATM") == 0) // PDB format
            {
                infmt = 0;
                x[0] = atof(line.substr(30, 8).c_str());
                x[1] = atof(line.substr(38, 8).c_str());
                x[2] = atof(line.substr(46, 8).c_str());
                transform(t, u, x, x1);
                buf << line.substr(0, 30) << setiosflags(ios::fixed)
                    << setprecision(3)
                    << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2]
                    << line.substr(54) << '\n';
            }
            else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
            {
                infmt = 3;
                buf << line << '\n';
                while (1)
                {
                    if (compress_type)
                    {
                        if (fin_gz.good())
                            getline(fin_gz, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + name);
                    }
                    else
                    {
                        if (fin.good())
                            getline(fin, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + name);
                    }
                    if (line.size())
                        break;
                }
                buf << line << '\n';
                if (line.compare(0, 11, "_atom_site."))
                    continue;
                _atom_site.clear();
                atom_site_pos = 0;
                _atom_site[Trim(line.substr(11))] = atom_site_pos;
                while (1)
                {
                    while (1)
                    {
                        if (compress_type)
                        {
                            if (fin_gz.good())
                                getline(fin_gz, line);
                            else
                                PrintErrorAndQuit("ERROR! Unexpected end of " + name);
                        }
                        else
                        {
                            if (fin.good())
                                getline(fin, line);
                            else
                                PrintErrorAndQuit("ERROR! Unexpected end of " + name);
                        }
                        if (line.size())
                            break;
                    }
                    if (line.compare(0, 11, "_atom_site."))
                        break;
                    _atom_site[Trim(line.substr(11))] = ++atom_site_pos;
                    buf << line << '\n';
                }

                if (_atom_site.count("group_PDB") *
                        _atom_site.count("Cartn_x") *
                        _atom_site.count("Cartn_y") *
                        _atom_site.count("Cartn_z") ==
                    0)
                {
                    buf << line << '\n';
                    cerr << "Warning! Missing one of the following _atom_site data items: group_PDB, Cartn_x, Cartn_y, Cartn_z" << endl;
                    continue;
                }

                while (1)
                {
                    line_vec.clear();
                    split(line, line_vec);
                    if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                        line_vec[_atom_site["group_PDB"]] != "HETATM")
                        break;

                    x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                    x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                    x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                    transform(t, u, x, x1);

                    for (atom_site_pos = 0; atom_site_pos < _atom_site.size();
                         atom_site_pos++)
                    {
                        if (atom_site_pos == _atom_site["Cartn_x"])
                            buf << setiosflags(ios::fixed) << setprecision(3)
                                << setw(8) << x1[0] << ' ';
                        else if (atom_site_pos == _atom_site["Cartn_y"])
                            buf << setiosflags(ios::fixed) << setprecision(3)
                                << setw(8) << x1[1] << ' ';
                        else if (atom_site_pos == _atom_site["Cartn_z"])
                            buf << setiosflags(ios::fixed) << setprecision(3)
                                << setw(8) << x1[2] << ' ';
                        else
                            buf << line_vec[atom_site_pos] << ' ';
                    }
                    buf << '\n';

                    if (compress_type && fin_gz.good())
                        getline(fin_gz, line);
                    else if (!compress_type && fin.good())
                        getline(fin, line);
                    else
                        break;
                }
                if (compress_type ? fin_gz.good() : fin.good())
                    buf << line << '\n';
            }
            else if (line.size())
                buf << line << '\n';
        }
        if (compress_type)
            fin_gz.close();
        else
            fin.close();

        ofstream fp;
        fp.open(filename.c_str());
        fp << buf.str();
        fp.close();
        buf.str(string()); // clear stream

        if (o_opt == 1)
            buf_pymol
                << "cmd.load(\"" << filename << "\",\"structure" << m << "\")\n"
                << "color " << color_list[m % color_list.size()] << ", structure" << m << "\n";
        else if (o_opt == 3)
            buf_pymol
                << "open \"" << filename << "\" name structure" << m << "\n"
                << "color #" << m + 1 << " " << color_list[m % color_list.size()] << "\n";
    }
    if (o_opt == 1)
        buf_pymol
            << "hide all\n"
            << "show cartoon\n"
            << "set ribbon_width, 6\n"
            << "set stick_radius, 0.3\n"
            << "set sphere_scale, 0.25\n"
            << "set ray_shadow, 0\n"
            << "bg_color white\n"
            << "set transparency=0.2\n"
            << endl;
    else if (o_opt == 3)
        buf_pymol
            << "set bgColor white\n"
            << endl;

    ofstream fp;
    if (o_opt == 1)
    {
        fp.open((fname_super + "_all_atm.pml").c_str());
        fp << buf_pymol.str();
        fp.close();
        fp.open((fname_super + "_all_atm_lig.pml").c_str());
        fp << buf_pymol.str()
           << "show stick, not polymer\n"
           << "show sphere, not polymer\n"
           << endl;
    }
    else if (o_opt == 3)
    {
        fp.open((fname_super + "_all_atm.cxc").c_str());
        fp << buf_pymol.str()
           << "hide ligand bonds\n"
           << "hide ligand atoms\n"
           << "hide solvent bonds\n"
           << "hide solvent atoms\n"
           << "hide ions bonds\n"
           << "hide ions atoms\n"
           << endl;
        fp.close();
        fp.open((fname_super + "_all_atm_lig.cxc").c_str());
        fp << buf_pymol.str() << endl;
    }

    fp.close();
    buf_pymol.str(string());
    vector<string>().swap(color_list);
}

void output_rasmol(const string xname, const string yname,
                   const string fname_super, double t[3], double u[3][3], const int ter_opt,
                   const int mm_opt, const int split_opt, const int mirror_opt,
                   const char *seqM, const char *seqxA, const char *seqyA,
                   const vector<string> &resi_vec1, const vector<string> &resi_vec2,
                   const string chainID1, const string chainID2,
                   const int xlen, const int ylen, const double d0A, const int n_ali8,
                   const double rmsd, const double TM1, const double Liden)
{
    stringstream buf;
    stringstream buf_all;
    stringstream buf_atm;
    stringstream buf_all_atm;
    stringstream buf_all_atm_lig;
    // stringstream buf_pdb;
    stringstream buf_tm;
    string line;
    double x[3];    // before transform
    double x1[3];   // after transform
    bool after_ter; // true if passed the "TER" line in PDB
    string asym_id; // chain ID

    buf_tm << "REMARK US-align"
           << "\nREMARK Structure 1:" << setw(11) << left << xname + chainID1 << " Size= " << xlen
           << "\nREMARK Structure 2:" << setw(11) << yname + chainID2 << right << " Size= " << ylen
           << " (TM-score is normalized by " << setw(4) << ylen << ", d0="
           << setiosflags(ios::fixed) << setprecision(2) << setw(6) << d0A << ")"
           << "\nREMARK Aligned length=" << setw(4) << n_ali8 << ", RMSD="
           << setw(6) << setiosflags(ios::fixed) << setprecision(2) << rmsd
           << ", TM-score=" << setw(7) << setiosflags(ios::fixed) << setprecision(5) << TM1
           << ", ID=" << setw(5) << setiosflags(ios::fixed) << setprecision(3)
           << ((n_ali8 > 0) ? Liden / n_ali8 : 0) << endl;
    string rasmol_CA_header = "load inline\nselect *A\nwireframe .45\nselect *B\nwireframe .20\nselect all\ncolor white\n";
    string rasmol_cartoon_header = "load inline\nselect all\ncartoon\nselect *A\ncolor blue\nselect *B\ncolor red\nselect ligand\nwireframe 0.25\nselect solvent\nspacefill 0.25\nselect all\nexit\n" + buf_tm.str();
    if (!mm_opt)
        buf << rasmol_CA_header;
    buf_all << rasmol_CA_header;
    if (!mm_opt)
        buf_atm << rasmol_cartoon_header;
    buf_all_atm << rasmol_cartoon_header;
    buf_all_atm_lig << rasmol_cartoon_header;

    /* selecting chains for -mol */
    string chain1_sele;
    string chain2_sele;
    int i;
    if (!mm_opt)
    {
        if (split_opt == 2 && ter_opt >= 1) // align one chain from model 1
        {
            chain1_sele = chainID1.substr(1);
            chain2_sele = chainID2.substr(1);
        }
        else if (split_opt == 2 && ter_opt == 0) // align one chain from each model
        {
            for (i = 1; i < chainID1.size(); i++)
                if (chainID1[i] == ',')
                    break;
            chain1_sele = chainID1.substr(i + 1);
            for (i = 1; i < chainID2.size(); i++)
                if (chainID2[i] == ',')
                    break;
            chain2_sele = chainID2.substr(i + 1);
        }
    }

    /* for PDBx/mmCIF only */
    map<string, int> _atom_site;
    int atom_site_pos;
    vector<string> line_vec;
    string atom;        // 4-character atom name
    string AA;          // 3-character residue name
    string resi;        // 4-character residue sequence number
    string inscode;     // 1-character insertion code
    string model_index; // model index
    bool is_mmcif = false;

    /* used for CONECT record of chain1 */
    int ca_idx1 = 0;  // all CA atoms
    int lig_idx1 = 0; // all atoms
    vector<int> idx_vec;

    /* used for CONECT record of chain2 */
    int ca_idx2 = 0;  // all CA atoms
    int lig_idx2 = 0; // all atoms

    /* extract aligned region */
    vector<string> resi_aln1;
    vector<string> resi_aln2;
    int i1 = -1;
    int i2 = -1;
    if (!mm_opt)
    {
        for (i = 0; i < strlen(seqM); i++)
        {
            i1 += (seqxA[i] != '-');
            i2 += (seqyA[i] != '-');
            if (seqM[i] == ' ')
                continue;
            resi_aln1.push_back(resi_vec1[i1].substr(0, 4));
            resi_aln2.push_back(resi_vec2[i2].substr(0, 4));
            if (seqM[i] != ':')
                continue;
            buf << "select " << resi_aln1.back() << ":A,"
                << resi_aln2.back() << ":B\ncolor red\n";
            buf_all << "select " << resi_aln1.back() << ":A,"
                    << resi_aln2.back() << ":B\ncolor red\n";
        }
        buf << "select all\nexit\n"
            << buf_tm.str();
    }
    buf_all << "select all\nexit\n"
            << buf_tm.str();

    ifstream fin;
    /* read first file */
    after_ter = false;
    asym_id = "";
    fin.open(xname.c_str());
    while (fin.good())
    {
        getline(fin, line);
        if (ter_opt >= 3 && line.compare(0, 3, "TER") == 0)
            after_ter = true;
        if (is_mmcif == false && line.size() >= 54 &&
            (line.compare(0, 6, "ATOM  ") == 0 ||
             line.compare(0, 6, "HETATM") == 0)) // PDB format
        {
            if (line[16] != 'A' && line[16] != ' ')
                continue;
            x[0] = atof(line.substr(30, 8).c_str());
            x[1] = atof(line.substr(38, 8).c_str());
            x[2] = atof(line.substr(46, 8).c_str());
            if (mirror_opt)
                x[2] = -x[2];
            transform(t, u, x, x1);
            // buf_pdb<<line.substr(0,30)<<setiosflags(ios::fixed)
            //<<setprecision(3)
            //<<setw(8)<<x1[0] <<setw(8)<<x1[1] <<setw(8)<<x1[2]
            //<<line.substr(54)<<'\n';

            if (after_ter && line.compare(0, 6, "ATOM  ") == 0)
                continue;
            lig_idx1++;
            buf_all_atm_lig << line.substr(0, 6) << setw(5) << lig_idx1
                            << line.substr(11, 9) << " A" << line.substr(22, 8)
                            << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (chain1_sele.size() && line[21] != chain1_sele[0])
                continue;
            if (after_ter || line.compare(0, 6, "ATOM  "))
                continue;
            if (ter_opt >= 2)
            {
                if (ca_idx1 && asym_id.size() && asym_id != line.substr(21, 1))
                {
                    after_ter = true;
                    continue;
                }
                asym_id = line[21];
            }
            buf_all_atm << "ATOM  " << setw(5) << lig_idx1
                        << line.substr(11, 9) << " A" << line.substr(22, 8)
                        << setiosflags(ios::fixed) << setprecision(3)
                        << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (!mm_opt && find(resi_aln1.begin(), resi_aln1.end(),
                                line.substr(22, 4)) != resi_aln1.end())
            {
                buf_atm << "ATOM  " << setw(5) << lig_idx1
                        << line.substr(11, 9) << " A" << line.substr(22, 8)
                        << setiosflags(ios::fixed) << setprecision(3)
                        << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            }
            if (line.substr(12, 4) != " CA " && line.substr(12, 4) != " C3'")
                continue;
            ca_idx1++;
            buf_all << "ATOM  " << setw(5) << ca_idx1 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " A" << line.substr(22, 8)
                    << setiosflags(ios::fixed) << setprecision(3)
                    << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (find(resi_aln1.begin(), resi_aln1.end(),
                     line.substr(22, 4)) == resi_aln1.end())
                continue;
            if (!mm_opt)
                buf << "ATOM  " << setw(5) << ca_idx1 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " A" << line.substr(22, 8)
                    << setiosflags(ios::fixed) << setprecision(3)
                    << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            idx_vec.push_back(ca_idx1);
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                if (line.size())
                    break;
            }
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[line.substr(11, line.size() - 12)] = atom_site_pos;
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                if (line.size() == 0)
                    continue;
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[line.substr(11, line.size() - 12)] = ++atom_site_pos;
            }

            if (is_mmcif == false)
            {
                // buf_pdb.str(string());
                is_mmcif = true;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;
                if (_atom_site.count("pdbx_PDB_model_num"))
                {
                    if (model_index.size() && model_index !=
                                                  line_vec[_atom_site["pdbx_PDB_model_num"]])
                        break;
                    model_index = line_vec[_atom_site["pdbx_PDB_model_num"]];
                }

                x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                if (mirror_opt)
                    x[2] = -x[2];
                transform(t, u, x, x1);

                if (_atom_site.count("label_alt_id") == 0 ||
                    line_vec[_atom_site["label_alt_id"]] == "." ||
                    line_vec[_atom_site["label_alt_id"]] == "A")
                {
                    atom = line_vec[_atom_site["label_atom_id"]];
                    if (atom[0] == '"')
                        atom = atom.substr(1);
                    if (atom.size() && atom[atom.size() - 1] == '"')
                        atom = atom.substr(0, atom.size() - 1);
                    if (atom.size() == 0)
                        atom = "    ";
                    else if (atom.size() == 1)
                        atom = " " + atom + "  ";
                    else if (atom.size() == 2)
                        atom = " " + atom + " ";
                    else if (atom.size() == 3)
                        atom = " " + atom;
                    else if (atom.size() >= 5)
                        atom = atom.substr(0, 4);

                    AA = line_vec[_atom_site["label_comp_id"]]; // residue name
                    if (AA.size() == 1)
                        AA = "  " + AA;
                    else if (AA.size() == 2)
                        AA = " " + AA;
                    else if (AA.size() >= 4)
                        AA = AA.substr(0, 3);

                    if (_atom_site.count("auth_seq_id"))
                        resi = line_vec[_atom_site["auth_seq_id"]];
                    else
                        resi = line_vec[_atom_site["label_seq_id"]];
                    while (resi.size() < 4)
                        resi = ' ' + resi;
                    if (resi.size() > 4)
                        resi = resi.substr(0, 4);

                    inscode = ' ';
                    if (_atom_site.count("pdbx_PDB_ins_code") &&
                        line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                        inscode = line_vec[_atom_site["pdbx_PDB_ins_code"]][0];

                    if (_atom_site.count("auth_asym_id"))
                    {
                        if (chain1_sele.size())
                            after_ter = line_vec[_atom_site["auth_asym_id"]] != chain1_sele;
                        else if (ter_opt >= 2 && ca_idx1 && asym_id.size() &&
                                 asym_id != line_vec[_atom_site["auth_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    }
                    else if (_atom_site.count("label_asym_id"))
                    {
                        if (chain1_sele.size())
                            after_ter = line_vec[_atom_site["label_asym_id"]] != chain1_sele;
                        if (ter_opt >= 2 && ca_idx1 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["label_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    }
                    // buf_pdb<<left<<setw(6)
                    //<<line_vec[_atom_site["group_PDB"]]<<right
                    //<<setw(5)<<lig_idx1%100000<<' '<<atom<<' '
                    //<<AA<<" "<<asym_id[asym_id.size()-1]
                    //<<resi<<inscode<<"   "
                    //<<setiosflags(ios::fixed)<<setprecision(3)
                    //<<setw(8)<<x1[0]
                    //<<setw(8)<<x1[1]
                    //<<setw(8)<<x1[2]<<'\n';

                    if (after_ter == false ||
                        line_vec[_atom_site["group_pdb"]] == "HETATM")
                    {
                        lig_idx1++;
                        buf_all_atm_lig << left << setw(6)
                                        << line_vec[_atom_site["group_PDB"]] << right
                                        << setw(5) << lig_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                        if (after_ter == false &&
                            line_vec[_atom_site["group_PDB"]] == "ATOM")
                        {
                            buf_all_atm << "ATOM  " << setw(6)
                                        << setw(5) << lig_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                            if (!mm_opt && find(resi_aln1.begin(),
                                                resi_aln1.end(), resi) != resi_aln1.end())
                            {
                                buf_atm << "ATOM  " << setw(6)
                                        << setw(5) << lig_idx1 % 100000 << ' '
                                        << atom << ' ' << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                            }
                            if (atom == " CA " || atom == " C3'")
                            {
                                ca_idx1++;
                                // mm_opt, split_opt, mirror_opt, chainID1,chainID2);
                                buf_all << "ATOM  " << setw(6)
                                        << setw(5) << ca_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                                if (!mm_opt && find(resi_aln1.begin(),
                                                    resi_aln1.end(), resi) != resi_aln1.end())
                                {
                                    buf << "ATOM  " << setw(6)
                                        << setw(5) << ca_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                                    idx_vec.push_back(ca_idx1);
                                }
                            }
                        }
                    }
                }

                while (1)
                {
                    if (fin.good())
                        getline(fin, line);
                    else
                        break;
                    if (line.size())
                        break;
                }
            }
        }
        else if (line.size() && is_mmcif == false)
        {
            // buf_pdb<<line<<'\n';
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    fin.close();
    if (!mm_opt)
        buf << "TER\n";
    buf_all << "TER\n";
    if (!mm_opt)
        buf_atm << "TER\n";
    buf_all_atm << "TER\n";
    buf_all_atm_lig << "TER\n";
    for (i = 1; i < ca_idx1; i++)
        buf_all << "CONECT"
                << setw(5) << i % 100000 << setw(5) << (i + 1) % 100000 << '\n';
    if (!mm_opt)
        for (i = 1; i < idx_vec.size(); i++)
            buf << "CONECT"
                << setw(5) << idx_vec[i - 1] % 100000 << setw(5) << idx_vec[i] % 100000 << '\n';
    idx_vec.clear();

    /* read second file */
    after_ter = false;
    asym_id = "";
    fin.open(yname.c_str());
    while (fin.good())
    {
        getline(fin, line);
        if (ter_opt >= 3 && line.compare(0, 3, "TER") == 0)
            after_ter = true;
        if (line.size() >= 54 && (line.compare(0, 6, "ATOM  ") == 0 ||
                                  line.compare(0, 6, "HETATM") == 0)) // PDB format
        {
            if (line[16] != 'A' && line[16] != ' ')
                continue;
            if (after_ter && line.compare(0, 6, "ATOM  ") == 0)
                continue;
            lig_idx2++;
            buf_all_atm_lig << line.substr(0, 6) << setw(5) << lig_idx1 + lig_idx2
                            << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            if (chain2_sele.size() && line[21] != chain2_sele[0])
                continue;
            if (after_ter || line.compare(0, 6, "ATOM  "))
                continue;
            if (ter_opt >= 2)
            {
                if (ca_idx2 && asym_id.size() && asym_id != line.substr(21, 1))
                {
                    after_ter = true;
                    continue;
                }
                asym_id = line[21];
            }
            buf_all_atm << "ATOM  " << setw(5) << lig_idx1 + lig_idx2
                        << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            if (!mm_opt && find(resi_aln2.begin(), resi_aln2.end(),
                                line.substr(22, 4)) != resi_aln2.end())
            {
                buf_atm << "ATOM  " << setw(5) << lig_idx1 + lig_idx2
                        << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            }
            if (line.substr(12, 4) != " CA " && line.substr(12, 4) != " C3'")
                continue;
            ca_idx2++;
            buf_all << "ATOM  " << setw(5) << ca_idx1 + ca_idx2 << ' ' << line.substr(12, 4)
                    << ' ' << line.substr(17, 3) << " B" << line.substr(22, 32) << '\n';
            if (find(resi_aln2.begin(), resi_aln2.end(), line.substr(22, 4)) == resi_aln2.end())
                continue;
            if (!mm_opt)
                buf << "ATOM  " << setw(5) << ca_idx1 + ca_idx2 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " B"
                    << line.substr(22, 32) << '\n';
            idx_vec.push_back(ca_idx1 + ca_idx2);
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + yname);
                if (line.size())
                    break;
            }
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[line.substr(11, line.size() - 12)] = atom_site_pos;
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + yname);
                if (line.size() == 0)
                    continue;
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[line.substr(11, line.size() - 12)] = ++atom_site_pos;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;
                if (_atom_site.count("pdbx_PDB_model_num"))
                {
                    if (model_index.size() && model_index !=
                                                  line_vec[_atom_site["pdbx_PDB_model_num"]])
                        break;
                    model_index = line_vec[_atom_site["pdbx_PDB_model_num"]];
                }

                if (_atom_site.count("label_alt_id") == 0 ||
                    line_vec[_atom_site["label_alt_id"]] == "." ||
                    line_vec[_atom_site["label_alt_id"]] == "A")
                {
                    atom = line_vec[_atom_site["label_atom_id"]];
                    if (atom[0] == '"')
                        atom = atom.substr(1);
                    if (atom.size() && atom[atom.size() - 1] == '"')
                        atom = atom.substr(0, atom.size() - 1);
                    if (atom.size() == 0)
                        atom = "    ";
                    else if (atom.size() == 1)
                        atom = " " + atom + "  ";
                    else if (atom.size() == 2)
                        atom = " " + atom + " ";
                    else if (atom.size() == 3)
                        atom = " " + atom;
                    else if (atom.size() >= 5)
                        atom = atom.substr(0, 4);

                    AA = line_vec[_atom_site["label_comp_id"]]; // residue name
                    if (AA.size() == 1)
                        AA = "  " + AA;
                    else if (AA.size() == 2)
                        AA = " " + AA;
                    else if (AA.size() >= 4)
                        AA = AA.substr(0, 3);

                    if (_atom_site.count("auth_seq_id"))
                        resi = line_vec[_atom_site["auth_seq_id"]];
                    else
                        resi = line_vec[_atom_site["label_seq_id"]];
                    while (resi.size() < 4)
                        resi = ' ' + resi;
                    if (resi.size() > 4)
                        resi = resi.substr(0, 4);

                    inscode = ' ';
                    if (_atom_site.count("pdbx_PDB_ins_code") &&
                        line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                        inscode = line_vec[_atom_site["pdbx_PDB_ins_code"]][0];

                    if (_atom_site.count("auth_asym_id"))
                    {
                        if (chain2_sele.size())
                            after_ter = line_vec[_atom_site["auth_asym_id"]] != chain2_sele;
                        if (ter_opt >= 2 && ca_idx2 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["auth_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    }
                    else if (_atom_site.count("label_asym_id"))
                    {
                        if (chain2_sele.size())
                            after_ter = line_vec[_atom_site["label_asym_id"]] != chain2_sele;
                        if (ter_opt >= 2 && ca_idx2 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["label_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    }
                    if (after_ter == false ||
                        line_vec[_atom_site["group_PDB"]] == "HETATM")
                    {
                        lig_idx2++;
                        buf_all_atm_lig << left << setw(6)
                                        << line_vec[_atom_site["group_PDB"]] << right
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                        if (after_ter == false &&
                            line_vec[_atom_site["group_PDB"]] == "ATOM")
                        {
                            buf_all_atm << "ATOM  " << setw(6)
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                            if (!mm_opt && find(resi_aln2.begin(),
                                                resi_aln2.end(), resi) != resi_aln2.end())
                            {
                                buf_atm << "ATOM  " << setw(6)
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                            }
                            if (atom == " CA " || atom == " C3'")
                            {
                                ca_idx2++;
                                buf_all << "ATOM  " << setw(6)
                                        << setw(5) << (ca_idx1 + ca_idx2) % 100000
                                        << ' ' << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                                if (!mm_opt && find(resi_aln2.begin(),
                                                    resi_aln2.end(), resi) != resi_aln2.end())
                                {
                                    buf << "ATOM  " << setw(6)
                                        << setw(5) << (ca_idx1 + ca_idx2) % 100000
                                        << ' ' << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                                    idx_vec.push_back(ca_idx1 + ca_idx2);
                                }
                            }
                        }
                    }
                }

                if (fin.good())
                    getline(fin, line);
                else
                    break;
            }
        }
        else if (line.size())
        {
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    fin.close();
    if (!mm_opt)
        buf << "TER\n";
    buf_all << "TER\n";
    if (!mm_opt)
        buf_atm << "TER\n";
    buf_all_atm << "TER\n";
    buf_all_atm_lig << "TER\n";
    for (i = ca_idx1 + 1; i < ca_idx1 + ca_idx2; i++)
        buf_all << "CONECT"
                << setw(5) << i % 100000 << setw(5) << (i + 1) % 100000 << '\n';
    for (i = 1; i < idx_vec.size(); i++)
        buf << "CONECT"
            << setw(5) << idx_vec[i - 1] % 100000 << setw(5) << idx_vec[i] % 100000 << '\n';
    idx_vec.clear();

    /* write pymol script */
    ofstream fp;
    /*
    stringstream buf_pymol;
    vector<string> pml_list;
    pml_list.push_back(fname_super+"");
    pml_list.push_back(fname_super+"_atm");
    pml_list.push_back(fname_super+"_all");
    pml_list.push_back(fname_super+"_all_atm");
    pml_list.push_back(fname_super+"_all_atm_lig");
    for (i=0;i<pml_list.size();i++)
    {
        buf_pymol<<"#!/usr/bin/env pymol\n"
            <<"load "<<pml_list[i]<<"\n"
            <<"hide all\n"
            <<((i==0 || i==2)?("show stick\n"):("show cartoon\n"))
            <<"color blue, chain A\n"
            <<"color red, chain B\n"
            <<"set ray_shadow, 0\n"
            <<"set stick_radius, 0.3\n"
            <<"set sphere_scale, 0.25\n"
            <<"show stick, not polymer\n"
            <<"show sphere, not polymer\n"
            <<"bg_color white\n"
            <<"set transparency=0.2\n"
            <<"zoom polymer\n"
            <<endl;
        fp.open((pml_list[i]+".pml").c_str());
        fp<<buf_pymol.str();
        fp.close();
        buf_pymol.str(string());
        pml_list[i].clear();
    }
    pml_list.clear();
    */

    /* write rasmol script */
    if (!mm_opt)
    {
        fp.open((fname_super+".spt").c_str());
        fp << buf.str();
        fp.close();
    }
    fp.open((fname_super + "_all.spt").c_str());
    fp << buf_all.str();
    fp.close();
    if (!mm_opt)
    {
        fp.open((fname_super + "_atm.spt").c_str());
        fp << buf_atm.str();
        fp.close();
    }
    fp.open((fname_super + "_all_atm.spt").c_str());
    fp << buf_all_atm.str();
    fp.close();
    fp.open((fname_super + "_all_atm_lig.spt").c_str());
    fp << buf_all_atm_lig.str();
    fp.close();
    // fp.open((fname_super+".pdb").c_str());
    // fp<<buf_pdb.str();
    // fp.close();

    /* clear stream */
    buf.str(string());
    buf_all.str(string());
    buf_atm.str(string());
    buf_all_atm.str(string());
    buf_all_atm_lig.str(string());
    // buf_pdb.str(string());
    buf_tm.str(string());
    resi_aln1.clear();
    resi_aln2.clear();
    asym_id.clear();
    line_vec.clear();
    atom.clear();
    AA.clear();
    resi.clear();
    inscode.clear();
    model_index.clear();
}

/* extract rotation matrix based on TMscore8 */
void output_rotation_matrix(const char *fname_matrix,
                            const double t[3], const double u[3][3])
{
    stringstream ss;
    ss << "------ The rotation matrix to rotate Structure_1 to Structure_2 ------\n";
    char dest[1000];
    sprintf(dest, "m %18s %14s %14s %14s\n", "t[m]", "u[m][0]", "u[m][1]", "u[m][2]");
    ss << string(dest);
    for (int k = 0; k < 3; k++)
    {
        sprintf(dest, "%d %18.10f %14.10f %14.10f %14.10f\n", k, t[k], u[k][0], u[k][1], u[k][2]);
        ss << string(dest);
    }
    ss << "\nCode for rotating Structure 1 from (x,y,z) to (X,Y,Z):\n"
          "for(i=0; i<L; i++)\n"
          "{\n"
          "   X[i] = t[0] + u[0][0]*x[i] + u[0][1]*y[i] + u[0][2]*z[i];\n"
          "   Y[i] = t[1] + u[1][0]*x[i] + u[1][1]*y[i] + u[1][2]*z[i];\n"
          "   Z[i] = t[2] + u[2][0]*x[i] + u[2][1]*y[i] + u[2][2]*z[i];\n"
          "}\n";
    if (strcmp(fname_matrix, (char *)("-")) == 0)
        cout << ss.str();
    else
    {
        fstream fout;
        fout.open(fname_matrix, ios::out | ios::trunc);
        if (fout)
        {
            fout << ss.str();
            fout.close();
        }
        else
            cout << "Open file to output rotation matrix fail.\n";
    }
    ss.str(string());
}

// output the final results
void output_results(const string xname, const string yname,
                    const string chainID1, const string chainID2,
                    const int xlen, const int ylen, double t[3], double u[3][3],
                    const double TM1, const double TM2,
                    const double TM3, const double TM4, const double TM5,
                    const double rmsd, const double d0_out, const char *seqM,
                    const char *seqxA, const char *seqyA, const double Liden,
                    const int n_ali8, const int L_ali, const double TM_ali,
                    const double rmsd_ali, const double TM_0, const double d0_0,
                    const double d0A, const double d0B, const double Lnorm_ass,
                    const double d0_scale, const double d0a, const double d0u,
                    const char *fname_matrix, const int outfmt_opt, const int ter_opt,
                    const int mm_opt, const int split_opt, const int o_opt,
                    const string fname_super, const int i_opt, const int a_opt,
                    const bool u_opt, const bool d_opt, const int mirror_opt,
                    const vector<string> &resi_vec1, const vector<string> &resi_vec2)
{
    if (outfmt_opt <= 0)
    {
        printf("\nName of Structure_1: %s%s (to be superimposed onto Structure_2)\n",
               xname.c_str(), chainID1.c_str());
        printf("Name of Structure_2: %s%s\n", yname.c_str(), chainID2.c_str());
        printf("Length of Structure_1: %d residues\n", xlen);
        printf("Length of Structure_2: %d residues\n\n", ylen);

        if (i_opt)
            printf("User-specified initial alignment: TM/Lali/rmsd = %7.5lf, %4d, %6.3lf\n", TM_ali, L_ali, rmsd_ali);

        printf("Aligned length= %d, RMSD= %6.2f, Seq_ID=n_identical/n_aligned= %4.3f\n", n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);
        printf("TM-score= %6.5f (normalized by length of Structure_1: L=%d, d0=%.2f)\n", TM2, xlen, d0B);
        printf("TM-score= %6.5f (normalized by length of Structure_2: L=%d, d0=%.2f)\n", TM1, ylen, d0A);

        if (a_opt == 1)
            printf("TM-score= %6.5f (if normalized by average length of two structures: L=%.1f, d0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);
        if (u_opt)
            printf("TM-score= %6.5f (normalized by user-specified L=%.2f and d0=%.2f)\n", TM4, Lnorm_ass, d0u);
        if (d_opt)
            printf("TM-score= %6.5f (scaled by user-specified d0=%.2f, and L=%d)\n", TM5, d0_scale, ylen);
        printf("(You should use TM-score normalized by length of the reference structure)\n");

        // output alignment
        printf("\n(\":\" denotes residue pairs of d <%4.1f Angstrom, ", d0_out);
        printf("\".\" denotes other aligned residues)\n");
        printf("%s\n", seqxA);
        printf("%s\n", seqM);
        printf("%s\n", seqyA);
    }
    else if (outfmt_opt == 1)
    {
        printf(">%s%s\tL=%d\td0=%.2f\tseqID=%.3f\tTM-score=%.5f\n",
               xname.c_str(), chainID1.c_str(), xlen, d0B, Liden / xlen, TM2);
        printf("%s\n", seqxA);
        printf(">%s%s\tL=%d\td0=%.2f\tseqID=%.3f\tTM-score=%.5f\n",
               yname.c_str(), chainID2.c_str(), ylen, d0A, Liden / ylen, TM1);
        printf("%s\n", seqyA);

        printf("# Lali=%d\tRMSD=%.2f\tseqID_ali=%.3f\n",
               n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);

        if (i_opt)
            printf("# User-specified initial alignment: TM=%.5lf\tLali=%4d\trmsd=%.3lf\n", TM_ali, L_ali, rmsd_ali);

        if (a_opt)
            printf("# TM-score=%.5f (normalized by average length of two structures: L=%.1f\td0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);

        if (u_opt)
            printf("# TM-score=%.5f (normalized by user-specified L=%.2f\td0=%.2f)\n", TM4, Lnorm_ass, d0u);

        if (d_opt)
            printf("# TM-score=%.5f (scaled by user-specified d0=%.2f\tL=%d)\n", TM5, d0_scale, ylen);

        printf("$$$$\n");
    }
    else if (outfmt_opt == 2)
    {
        printf("%s%s\t%s%s\t%.4f\t%.4f\t%.2f\t%4.3f\t%4.3f\t%4.3f\t%d\t%d\t%d",
               xname.c_str(), chainID1.c_str(), yname.c_str(), chainID2.c_str(),
               TM2, TM1, rmsd, Liden / xlen, Liden / ylen, (n_ali8 > 0) ? Liden / n_ali8 : 0,
               xlen, ylen, n_ali8);
    }
    if (outfmt_opt < 5)
        cout << endl;

    if (strlen(fname_matrix))
        output_rotation_matrix(fname_matrix, t, u);

    if (o_opt == 1 || o_opt == 3)
        output_pymol(xname, yname, fname_super, t, u, ter_opt,
                     mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                     resi_vec1, resi_vec2, chainID1, chainID2, o_opt);
    else if (o_opt == 2)
        output_rasmol(xname, yname, fname_super, t, u, ter_opt,
                      mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                      resi_vec1, resi_vec2, chainID1, chainID2,
                      xlen, ylen, d0A, n_ali8, rmsd, TM1, Liden);
}

void output_mTMalign_results(const string xname, const string yname,
                             const string chainID1, const string chainID2,
                             const int xlen, const int ylen, double t[3], double u[3][3],
                             const double TM1, const double TM2,
                             const double TM3, const double TM4, const double TM5,
                             const double rmsd, const double d0_out, const char *seqM,
                             const char *seqxA, const char *seqyA, const double Liden,
                             const int n_ali8, const int L_ali, const double TM_ali,
                             const double rmsd_ali, const double TM_0, const double d0_0,
                             const double d0A, const double d0B, const double Lnorm_ass,
                             const double d0_scale, const double d0a, const double d0u,
                             const char *fname_matrix, const int outfmt_opt, const int ter_opt,
                             const int mm_opt, const int split_opt, const int o_opt,
                             const string fname_super, const int i_opt, const int a_opt,
                             const bool u_opt, const bool d_opt, const int mirror_opt,
                             const vector<string> &resi_vec1, const vector<string> &resi_vec2)
{
    if (outfmt_opt <= 0)
    {
        printf("Average aligned length= %d, RMSD= %6.2f, Seq_ID=n_identical/n_aligned= %4.3f\n", n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);
        printf("Average TM-score= %6.5f (normalized by length of shorter structure: L=%d, d0=%.2f)\n", TM2, xlen, d0B);
        printf("Average TM-score= %6.5f (normalized by length of longer structure: L=%d, d0=%.2f)\n", TM1, ylen, d0A);

        if (a_opt == 1)
            printf("Average TM-score= %6.5f (if normalized by average length of two structures: L=%.1f, d0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);
        if (u_opt)
            printf("Average TM-score= %6.5f (normalized by average L=%.2f and d0=%.2f)\n", TM4, Lnorm_ass, d0u);
        if (d_opt)
            printf("Average TM-score= %6.5f (scaled by user-specified d0=%.2f, and L=%d)\n", TM5, d0_scale, ylen);

        // output alignment
        printf("In the following, seqID=n_identical/L.\n\n%s\n", seqM);
    }
    else if (outfmt_opt == 1)
    {
        printf("%s\n", seqM);

        printf("# Lali=%d\tRMSD=%.2f\tseqID_ali=%.3f\n",
               n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);

        if (i_opt)
            printf("# User-specified initial alignment: TM=%.5lf\tLali=%4d\trmsd=%.3lf\n", TM_ali, L_ali, rmsd_ali);

        if (a_opt)
            printf("# TM-score=%.5f (normalized by average length of two structures: L=%.1f\td0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);

        if (u_opt)
            printf("# TM-score=%.5f (normalized by average L=%.2f\td0=%.2f)\n", TM4, Lnorm_ass, d0u);

        if (d_opt)
            printf("# TM-score=%.5f (scaled by user-specified d0=%.2f\tL=%d)\n", TM5, d0_scale, ylen);

        printf("$$$$\n");
    }
    else if (outfmt_opt == 2)
    {
        printf("%s%s\t%s%s\t%.4f\t%.4f\t%.2f\t%4.3f\t%4.3f\t%4.3f\t%d\t%d\t%d",
               xname.c_str(), chainID1.c_str(), yname.c_str(), chainID2.c_str(),
               TM2, TM1, rmsd, Liden / xlen, Liden / ylen, (n_ali8 > 0) ? Liden / n_ali8 : 0,
               xlen, ylen, n_ali8);
    }
    cout << endl;

    if (strlen(fname_matrix))
        output_rotation_matrix(fname_matrix, t, u);

    if (o_opt == 1 || o_opt == 3)
        output_pymol(xname, yname, fname_super, t, u, ter_opt,
                     mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                     resi_vec1, resi_vec2, chainID1, chainID2, o_opt);
    else if (o_opt == 2)
        output_rasmol(xname, yname, fname_super, t, u, ter_opt,
                      mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                      resi_vec1, resi_vec2, chainID1, chainID2,
                      xlen, ylen, d0A, n_ali8, rmsd, TM1, Liden);
}

double standard_TMscore(double **r1, double **r2, double **xtm, double **ytm,
                        double **xt, double **x, double **y, int xlen, int ylen, int invmap[],
                        int &L_ali, double &RMSD, double D0_MIN, double Lnorm, double d0,
                        double d0_search, double score_d8, double t[3], double u[3][3],
                        const int mol_type)
{
    D0_MIN = 0.5;
    Lnorm = ylen;
    if (mol_type > 0) // RNA
    {
        if (Lnorm <= 11)
            d0 = 0.3;
        else if (Lnorm > 11 && Lnorm <= 15)
            d0 = 0.4;
        else if (Lnorm > 15 && Lnorm <= 19)
            d0 = 0.5;
        else if (Lnorm > 19 && Lnorm <= 23)
            d0 = 0.6;
        else if (Lnorm > 23 && Lnorm < 30)
            d0 = 0.7;
        else
            d0 = (0.6 * pow((Lnorm * 1.0 - 0.5), 1.0 / 2) - 2.5);
    }
    else
    {
        if (Lnorm > 21)
            d0 = (1.24 * pow((Lnorm * 1.0 - 15), 1.0 / 3) - 1.8);
        else
            d0 = D0_MIN;
        if (d0 < D0_MIN)
            d0 = D0_MIN;
    }
    double d0_input = d0; // Scaled by seq_min

    double tmscore; // collected alined residues from invmap
    int n_al = 0;
    int i;
    for (int j = 0; j < ylen; j++)
    {
        i = invmap[j];
        if (i >= 0)
        {
            xtm[n_al][0] = x[i][0];
            xtm[n_al][1] = x[i][1];
            xtm[n_al][2] = x[i][2];

            ytm[n_al][0] = y[j][0];
            ytm[n_al][1] = y[j][1];
            ytm[n_al][2] = y[j][2];

            r1[n_al][0] = x[i][0];
            r1[n_al][1] = x[i][1];
            r1[n_al][2] = x[i][2];

            r2[n_al][0] = y[j][0];
            r2[n_al][1] = y[j][1];
            r2[n_al][2] = y[j][2];

            n_al++;
        }
        else if (i != -1)
            PrintErrorAndQuit("Wrong map!\n");
    }
    L_ali = n_al;

    Kabsch(r1, r2, n_al, 0, &RMSD, t, u);
    RMSD = sqrt(RMSD / (1.0 * n_al));

    int temp_simplify_step = 1;
    int temp_score_sum_method = 0;
    d0_search = d0_input;
    double rms = 0.0;
    tmscore = TMscore8_search_standard(r1, r2, xtm, ytm, xt, n_al, t, u,
                                       temp_simplify_step, temp_score_sum_method, &rms, d0_input,
                                       score_d8, d0);
    tmscore = tmscore * n_al / (1.0 * Lnorm);

    return tmscore;
}

/* copy the value of t and u into t0,u0 */
void copy_t_u(double t[3], double u[3][3], double t0[3], double u0[3][3])
{
    int i, j;
    for (i = 0; i < 3; i++)
    {
        t0[i] = t[i];
        for (j = 0; j < 3; j++)
            u0[i][j] = u[i][j];
    }
}

/* calculate approximate TM-score given rotation matrix */
double approx_TM(const int xlen, const int ylen, const int a_opt,
                 double **xa, double **ya, double t[3], double u[3][3],
                 const int invmap0[], const int mol_type)
{
    double Lnorm_0 = ylen; // normalized by the second protein
    if (a_opt == -2 && xlen > ylen)
        Lnorm_0 = xlen; // longer
    else if (a_opt == -1 && xlen < ylen)
        Lnorm_0 = xlen; // shorter
    else if (a_opt == 1)
        Lnorm_0 = (xlen + ylen) / 2.; // average

    double D0_MIN;
    double Lnorm;
    double d0;
    double d0_search;
    parameter_set4final(Lnorm_0, D0_MIN, Lnorm, d0, d0_search, mol_type);
    double TMtmp = 0;
    double d;
    double xtmp[3] = {0, 0, 0};

    for (int i = 0, j = 0; j < ylen; j++)
    {
        i = invmap0[j];
        if (i >= 0) // aligned
        {
            transform(t, u, &xa[i][0], &xtmp[0]);
            d = sqrt(dist(&xtmp[0], &ya[j][0]));
            TMtmp += 1 / (1 + (d / d0) * (d / d0));
            // if (d <= score_d8) TMtmp+=1/(1+(d/d0)*(d/d0));
        }
    }
    TMtmp /= Lnorm_0;
    return TMtmp;
}

void clean_up_after_approx_TM(int *invmap0, int *invmap,
                              double **score, bool **path, double **val, double **xtm, double **ytm,
                              double **xt, double **r1, double **r2, const int xlen, const int minlen)
{
    delete[] invmap0;
    delete[] invmap;
    DeleteArray(&score, xlen + 1);
    DeleteArray(&path, xlen + 1);
    DeleteArray(&val, xlen + 1);
    DeleteArray(&xtm, minlen);
    DeleteArray(&ytm, minlen);
    DeleteArray(&xt, xlen);
    DeleteArray(&r1, minlen);
    DeleteArray(&r2, minlen);
    return;
}

/* Entry function for TM-align. Return TM-score calculation status:
 * 0   - full TM-score calculation
 * 1   - terminated due to exception
 * 2-7 - pre-terminated due to low TM-score */
int TMalign_main(double **xa, double **ya,
                 const char *seqx, const char *seqy, const char *secx, const char *secy,
                 double t0[3], double u0[3][3],
                 double &TM1, double &TM2, double &TM3, double &TM4, double &TM5,
                 double &d0_0, double &TM_0,
                 double &d0A, double &d0B, double &d0u, double &d0a, double &d0_out,
                 string &seqM, string &seqxA, string &seqyA, vector<double> &do_vec,
                 double &rmsd0, int &L_ali, double &Liden,
                 double &TM_ali, double &rmsd_ali, int &n_ali, int &n_ali8,
                 const int xlen, const int ylen,
                 const vector<string> sequence, const double Lnorm_ass,
                 const double d0_scale, const int i_opt, const int a_opt,
                 const bool u_opt, const bool d_opt, const bool fast_opt,
                 const int mol_type, const double TMcut, const int ss_opt)
{
    double D0_MIN;                        // for d0
    double Lnorm;                         // normalization length
    double score_d8, d0, d0_search, dcu0; // for TMscore search
    double t[3], u[3][3];                 // Kabsch translation vector and rotation matrix
    double **score;                       // Input score table for dynamic programming
    bool **path;                          // for dynamic programming
    double **val;                         // for dynamic programming
    double **xtm, **ytm;                  // for TMscore search engine
    double **xt;                          // for saving the superposed version of r_1 or xtm
    double **r1, **r2;                    // for Kabsch rotation

    /***********************/
    /* allocate memory     */
    /***********************/
    int minlen = min(xlen, ylen);
    NewArray(&score, xlen + 1, ylen + 1);
    NewArray(&path, xlen + 1, ylen + 1);
    NewArray(&val, xlen + 1, ylen + 1);
    NewArray(&xtm, minlen, 3);
    NewArray(&ytm, minlen, 3);
    NewArray(&xt, xlen, 3);
    NewArray(&r1, minlen, 3);
    NewArray(&r2, minlen, 3);

    /***********************/
    /* parameter set    */
    /***********************/
    parameter_set4search(xlen, ylen, D0_MIN, Lnorm,
                         score_d8, d0, d0_search, dcu0);
    int simplify_step = 40;   // for simplified search engine
    int score_sum_method = 8; // for scoring method, whether only sum over pairs with dis<score_d8

    int i;
    int *invmap0 = new int[ylen + 1];
    int *invmap = new int[ylen + 1];
    double TM, TMmax = -1;
    for (i = 0; i < ylen; i++)
        invmap0[i] = -1;

    double ddcc = 0.4;
    if (Lnorm <= 40)
        ddcc = 0.1; // Lnorm was setted in parameter_set4search
    double local_d0_search = d0_search;

    //************************************************//
    //    get initial alignment from user's input:    //
    //    Stick to the initial alignment              //
    //************************************************//
    if (i_opt == 3) // if input has set parameter for "-I"
    {
        // In the original code, this loop starts from 1, which is
        // incorrect. Fortran starts from 1 but C++ should starts from 0.
        for (int j = 0; j < ylen; j++) // Set aligned position to be "-1"
            invmap[j] = -1;

        int i1 = -1; // in C version, index starts from zero, not from one
        int i2 = -1;
        int L1 = sequence[0].size();
        int L2 = sequence[1].size();
        int L = min(L1, L2); // Get positions for aligned residues
        for (int kk1 = 0; kk1 < L; kk1++)
        {
            if (sequence[0][kk1] != '-')
                i1++;
            if (sequence[1][kk1] != '-')
            {
                i2++;
                if (i2 >= ylen || i1 >= xlen)
                    kk1 = L;
                else if (sequence[0][kk1] != '-')
                    invmap[i2] = i1;
            }
        }

        //--------------- 2. Align proteins from original alignment
        double prevD0_MIN = D0_MIN; // stored for later use
        int prevLnorm = Lnorm;
        double prevd0 = d0;
        TM_ali = standard_TMscore(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen,
                                  invmap, L_ali, rmsd_ali, D0_MIN, Lnorm, d0, d0_search, score_d8,
                                  t, u, mol_type);
        D0_MIN = prevD0_MIN;
        Lnorm = prevLnorm;
        d0 = prevd0;
        TM = detailed_search_standard(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen,
                                      invmap, t, u, 40, 8, local_d0_search, true, Lnorm, score_d8, d0);
        if (TM > TMmax)
        {
            TMmax = TM;
            for (i = 0; i < ylen; i++)
                invmap0[i] = invmap[i];
        }
    }

    /******************************************************/
    /* get initial alignment with gapless threading    */
    /******************************************************/
    if (i_opt <= 1)
    {
        get_initial(r1, r2, xtm, ytm, xa, ya, xlen, ylen, invmap0, d0,
                    d0_search, fast_opt, t, u);
        TM = detailed_search(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen, invmap0,
                             t, u, simplify_step, score_sum_method, local_d0_search, Lnorm,
                             score_d8, d0);
        if (TM > TMmax)
            TMmax = TM;
        if (TMcut > 0)
            copy_t_u(t, u, t0, u0);
        // run dynamic programing iteratively to find the best alignment
        TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya, xlen, ylen,
                     t, u, invmap, 0, 2, (fast_opt) ? 2 : 30, local_d0_search,
                     D0_MIN, Lnorm, d0, score_d8);
        if (TM > TMmax)
        {
            TMmax = TM;
            for (int i = 0; i < ylen; i++)
                invmap0[i] = invmap[i];
            if (TMcut > 0)
                copy_t_u(t, u, t0, u0);
        }

        if (TMcut > 0) // pre-terminate if TM-score is too low
        {
            double TMtmp = approx_TM(xlen, ylen, a_opt,
                                     xa, ya, t0, u0, invmap0, mol_type);

            if (TMtmp < 0.5 * TMcut)
            {
                TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
                clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                         xtm, ytm, xt, r1, r2, xlen, minlen);
                return 2;
            }
        }

        /************************************************************/
        /* get initial alignment based on secondary structure    */
        /************************************************************/
        if (ss_opt != 1)
        {
            get_initial_ss(path, val, secx, secy, xlen, ylen, invmap);
            TM = detailed_search(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen, invmap,
                                 t, u, simplify_step, score_sum_method, local_d0_search, Lnorm,
                                 score_d8, d0);
            if (TM > TMmax)
            {
                TMmax = TM;
                for (int i = 0; i < ylen; i++)
                    invmap0[i] = invmap[i];
                if (TMcut > 0)
                    copy_t_u(t, u, t0, u0);
            }
            if (TM > TMmax * 0.2)
            {
                TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya,
                             xlen, ylen, t, u, invmap, 0, 2, (fast_opt) ? 2 : 30,
                             local_d0_search, D0_MIN, Lnorm, d0, score_d8);
                if (TM > TMmax)
                {
                    TMmax = TM;
                    for (int i = 0; i < ylen; i++)
                        invmap0[i] = invmap[i];
                    if (TMcut > 0)
                        copy_t_u(t, u, t0, u0);
                }
            }

            if (TMcut > 0) // pre-terminate if TM-score is too low
            {
                double TMtmp = approx_TM(xlen, ylen, a_opt,
                                         xa, ya, t0, u0, invmap0, mol_type);

                if (TMtmp < 0.52 * TMcut)
                {
                    TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
                    clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                             xtm, ytm, xt, r1, r2, xlen, minlen);
                    return 3;
                }
            }
        }

        /************************************************************/
        /* get initial alignment based on local superposition    */
        /************************************************************/
        //=initial5 in original TM-align
        if (ss_opt != 1)
        {
            if (get_initial5(r1, r2, xtm, ytm, path, val, xa, ya,
                             xlen, ylen, invmap, d0, d0_search, fast_opt, D0_MIN))
            {
                TM = detailed_search(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen,
                                     invmap, t, u, simplify_step, score_sum_method,
                                     local_d0_search, Lnorm, score_d8, d0);
                if (TM > TMmax)
                {
                    TMmax = TM;
                    for (int i = 0; i < ylen; i++)
                        invmap0[i] = invmap[i];
                    if (TMcut > 0)
                        copy_t_u(t, u, t0, u0);
                }
                if (TM > TMmax * ddcc)
                {
                    TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya,
                                 xlen, ylen, t, u, invmap, 0, 2, 2, local_d0_search,
                                 D0_MIN, Lnorm, d0, score_d8);
                    if (TM > TMmax)
                    {
                        TMmax = TM;
                        for (int i = 0; i < ylen; i++)
                            invmap0[i] = invmap[i];
                        if (TMcut > 0)
                            copy_t_u(t, u, t0, u0);
                    }
                }
            }
            else
                cerr << "\n\nWarning: initial alignment from local superposition fail!\n\n"
                     << endl;
        }

        if (TMcut > 0) // pre-terminate if TM-score is too low
        {
            double TMtmp = approx_TM(xlen, ylen, a_opt,
                                     xa, ya, t0, u0, invmap0, mol_type);

            if (TMtmp < 0.54 * TMcut)
            {
                TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
                clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                         xtm, ytm, xt, r1, r2, xlen, minlen);
                return 4;
            }
        }

        /********************************************************************/
        /* get initial alignment by local superposition+secondary structure */
        /********************************************************************/
        //=initial3 in original TM-align
        if (ss_opt != 1)
        {
            get_initial_ssplus(r1, r2, score, path, val, secx, secy, xa, ya,
                               xlen, ylen, invmap0, invmap, D0_MIN, d0);
            TM = detailed_search(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen, invmap,
                                 t, u, simplify_step, score_sum_method, local_d0_search, Lnorm,
                                 score_d8, d0);
            if (TM > TMmax)
            {
                TMmax = TM;
                for (i = 0; i < ylen; i++)
                    invmap0[i] = invmap[i];
                if (TMcut > 0)
                    copy_t_u(t, u, t0, u0);
            }
            if (TM > TMmax * ddcc)
            {
                TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya,
                             xlen, ylen, t, u, invmap, 0, 2, (fast_opt) ? 2 : 30,
                             local_d0_search, D0_MIN, Lnorm, d0, score_d8);
                if (TM > TMmax)
                {
                    TMmax = TM;
                    for (i = 0; i < ylen; i++)
                        invmap0[i] = invmap[i];
                    if (TMcut > 0)
                        copy_t_u(t, u, t0, u0);
                }
            }

            if (TMcut > 0) // pre-terminate if TM-score is too low
            {
                double TMtmp = approx_TM(xlen, ylen, a_opt,
                                         xa, ya, t0, u0, invmap0, mol_type);

                if (TMtmp < 0.56 * TMcut)
                {
                    TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
                    clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                             xtm, ytm, xt, r1, r2, xlen, minlen);
                    return 5;
                }
            }
        }

        /*******************************************************************/
        /* get initial alignment based on fragment gapless threading    */
        /*******************************************************************/
        //=initial4 in original TM-align
        get_initial_fgt(r1, r2, xtm, ytm, xa, ya, xlen, ylen,
                        invmap, d0, d0_search, dcu0, fast_opt, t, u);
        TM = detailed_search(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen, invmap,
                             t, u, simplify_step, score_sum_method, local_d0_search, Lnorm,
                             score_d8, d0);
        if (TM > TMmax)
        {
            TMmax = TM;
            for (i = 0; i < ylen; i++)
                invmap0[i] = invmap[i];
            if (TMcut > 0)
                copy_t_u(t, u, t0, u0);
        }
        if (TM > TMmax * ddcc)
        {
            TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya,
                         xlen, ylen, t, u, invmap, 1, 2, 2, local_d0_search, D0_MIN,
                         Lnorm, d0, score_d8);
            if (TM > TMmax)
            {
                TMmax = TM;
                for (i = 0; i < ylen; i++)
                    invmap0[i] = invmap[i];
                if (TMcut > 0)
                    copy_t_u(t, u, t0, u0);
            }
        }

        if (TMcut > 0) // pre-terminate if TM-score is too low
        {
            double TMtmp = approx_TM(xlen, ylen, a_opt,
                                     xa, ya, t0, u0, invmap0, mol_type);

            if (TMtmp < 0.58 * TMcut)
            {
                TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
                clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                         xtm, ytm, xt, r1, r2, xlen, minlen);
                return 6;
            }
        }
    }

    //************************************************//
    //    get initial alignment from user's input:    //
    //************************************************//
    if (i_opt >= 1 && i_opt <= 2) // if input has set parameter for "-i"
    {
        for (int j = 0; j < ylen; j++) // Set aligned position to be "-1"
            invmap[j] = -1;

        int i1 = -1; // in C version, index starts from zero, not from one
        int i2 = -1;
        int L1 = sequence[0].size();
        int L2 = sequence[1].size();
        int L = min(L1, L2); // Get positions for aligned residues
        for (int kk1 = 0; kk1 < L; kk1++)
        {
            if (sequence[0][kk1] != '-')
                i1++;
            if (sequence[1][kk1] != '-')
            {
                i2++;
                if (i2 >= ylen || i1 >= xlen)
                    kk1 = L;
                else if (sequence[0][kk1] != '-')
                    invmap[i2] = i1;
            }
        }

        //--------------- 2. Align proteins from original alignment
        double prevD0_MIN = D0_MIN; // stored for later use
        int prevLnorm = Lnorm;
        double prevd0 = d0;
        TM_ali = standard_TMscore(r1, r2, xtm, ytm, xt, xa, ya,
                                  xlen, ylen, invmap, L_ali, rmsd_ali, D0_MIN, Lnorm, d0,
                                  d0_search, score_d8, t, u, mol_type);
        D0_MIN = prevD0_MIN;
        Lnorm = prevLnorm;
        d0 = prevd0;

        TM = detailed_search_standard(r1, r2, xtm, ytm, xt, xa, ya,
                                      xlen, ylen, invmap, t, u, 40, 8, local_d0_search, true, Lnorm,
                                      score_d8, d0);
        if (TM > TMmax)
        {
            TMmax = TM;
            for (i = 0; i < ylen; i++)
                invmap0[i] = invmap[i];
        }
        // Different from get_initial, get_initial_ss and get_initial_ssplus
        TM = DP_iter(r1, r2, xtm, ytm, xt, path, val, xa, ya,
                     xlen, ylen, t, u, invmap, 0, 2, (fast_opt) ? 2 : 30,
                     local_d0_search, D0_MIN, Lnorm, d0, score_d8);
        if (TM > TMmax)
        {
            TMmax = TM;
            for (i = 0; i < ylen; i++)
                invmap0[i] = invmap[i];
        }
    }

    //*******************************************************************//
    //    The alignment will not be changed any more in the following    //
    //*******************************************************************//
    // check if the initial alignment is generated appropriately
    bool flag = false;
    for (i = 0; i < ylen; i++)
    {
        if (invmap0[i] >= 0)
        {
            flag = true;
            break;
        }
    }
    if (!flag)
    {
        cout << "There is no alignment between the two structures! "
             << "Program stop with no result!" << endl;
        TM1 = TM2 = TM3 = TM4 = TM5 = 0;
        return 1;
    }

    /* last TM-score pre-termination */
    if (TMcut > 0)
    {
        double TMtmp = approx_TM(xlen, ylen, a_opt,
                                 xa, ya, t0, u0, invmap0, mol_type);

        if (TMtmp < 0.6 * TMcut)
        {
            TM1 = TM2 = TM3 = TM4 = TM5 = TMtmp;
            clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                                     xtm, ytm, xt, r1, r2, xlen, minlen);
            return 7;
        }
    }

    //********************************************************************//
    //    Detailed TMscore search engine --> prepare for final TMscore    //
    //********************************************************************//
    // run detailed TMscore search engine for the best alignment, and
    // extract the best rotation matrix (t, u) for the best alignment
    simplify_step = 1;
    if (fast_opt)
        simplify_step = 40;
    score_sum_method = 8;
    TM = detailed_search_standard(r1, r2, xtm, ytm, xt, xa, ya, xlen, ylen,
                                  invmap0, t, u, simplify_step, score_sum_method, local_d0_search,
                                  false, Lnorm, score_d8, d0);

    // select pairs with dis<d8 for final TMscore computation and output alignment
    int k = 0;
    int *m1, *m2;
    double d;
    m1 = new int[xlen]; // alignd index in x
    m2 = new int[ylen]; // alignd index in y
    do_rotation(xa, xt, xlen, t, u);
    k = 0;
    for (int j = 0; j < ylen; j++)
    {
        i = invmap0[j];
        if (i >= 0) // aligned
        {
            n_ali++;
            d = sqrt(dist(&xt[i][0], &ya[j][0]));
            if (d <= score_d8 || (i_opt == 3))
            {
                m1[k] = i;
                m2[k] = j;

                xtm[k][0] = xa[i][0];
                xtm[k][1] = xa[i][1];
                xtm[k][2] = xa[i][2];

                ytm[k][0] = ya[j][0];
                ytm[k][1] = ya[j][1];
                ytm[k][2] = ya[j][2];

                r1[k][0] = xt[i][0];
                r1[k][1] = xt[i][1];
                r1[k][2] = xt[i][2];
                r2[k][0] = ya[j][0];
                r2[k][1] = ya[j][1];
                r2[k][2] = ya[j][2];

                k++;
            }
        }
    }
    n_ali8 = k;

    Kabsch(r1, r2, n_ali8, 0, &rmsd0, t, u); // rmsd0 is used for final output, only recalculate rmsd0, not t & u
    rmsd0 = sqrt(rmsd0 / n_ali8);

    //****************************************//
    //              Final TMscore             //
    //    Please set parameters for output    //
    //****************************************//
    double rmsd;
    simplify_step = 1;
    score_sum_method = 0;
    double Lnorm_0 = ylen;

    // normalized by length of structure A
    parameter_set4final(Lnorm_0, D0_MIN, Lnorm, d0, d0_search, mol_type);
    d0A = d0;
    d0_0 = d0A;
    local_d0_search = d0_search;
    TM1 = TMscore8_search(r1, r2, xtm, ytm, xt, n_ali8, t0, u0, simplify_step,
                          score_sum_method, &rmsd, local_d0_search, Lnorm, score_d8, d0);
    TM_0 = TM1;

    // normalized by length of structure B
    parameter_set4final(xlen + 0.0, D0_MIN, Lnorm, d0, d0_search, mol_type);
    d0B = d0;
    local_d0_search = d0_search;
    TM2 = TMscore8_search(r1, r2, xtm, ytm, xt, n_ali8, t, u, simplify_step,
                          score_sum_method, &rmsd, local_d0_search, Lnorm, score_d8, d0);

    double Lnorm_d0;
    if (a_opt > 0)
    {
        // normalized by average length of structures A, B
        Lnorm_0 = (xlen + ylen) * 0.5;
        parameter_set4final(Lnorm_0, D0_MIN, Lnorm, d0, d0_search, mol_type);
        d0a = d0;
        d0_0 = d0a;
        local_d0_search = d0_search;

        TM3 = TMscore8_search(r1, r2, xtm, ytm, xt, n_ali8, t0, u0,
                              simplify_step, score_sum_method, &rmsd, local_d0_search, Lnorm,
                              score_d8, d0);
        TM_0 = TM3;
    }
    if (u_opt)
    {
        // normalized by user assigned length
        parameter_set4final(Lnorm_ass, D0_MIN, Lnorm,
                            d0, d0_search, mol_type);
        d0u = d0;
        d0_0 = d0u;
        Lnorm_0 = Lnorm_ass;
        local_d0_search = d0_search;
        TM4 = TMscore8_search(r1, r2, xtm, ytm, xt, n_ali8, t0, u0,
                              simplify_step, score_sum_method, &rmsd, local_d0_search, Lnorm,
                              score_d8, d0);
        TM_0 = TM4;
    }
    if (d_opt)
    {
        // scaled by user assigned d0
        parameter_set4scale(ylen, d0_scale, Lnorm, d0, d0_search);
        d0_out = d0_scale;
        d0_0 = d0_scale;
        // Lnorm_0=ylen;
        Lnorm_d0 = Lnorm_0;
        local_d0_search = d0_search;
        TM5 = TMscore8_search(r1, r2, xtm, ytm, xt, n_ali8, t0, u0,
                              simplify_step, score_sum_method, &rmsd, local_d0_search, Lnorm,
                              score_d8, d0);
        TM_0 = TM5;
    }

    /* derive alignment from superposition */
    int ali_len = xlen + ylen; // maximum length of alignment
    seqxA.assign(ali_len, '-');
    seqM.assign(ali_len, ' ');
    seqyA.assign(ali_len, '-');
    do_vec.clear();
    do_vec.assign(ali_len, 0);

    // do_rotation(xa, xt, xlen, t, u);
    do_rotation(xa, xt, xlen, t0, u0);

    int kk = 0, i_old = 0, j_old = 0;
    d = 0;
    Liden = 0;
    // double SO=0;
    for (int k = 0; k < n_ali8; k++)
    {
        for (int i = i_old; i < m1[k]; i++)
        {
            // align x to gap
            seqxA[kk] = seqx[i];
            seqyA[kk] = '-';
            seqM[kk] = ' ';
            kk++;
        }

        for (int j = j_old; j < m2[k]; j++)
        {
            // align y to gap
            seqxA[kk] = '-';
            seqyA[kk] = seqy[j];
            seqM[kk] = ' ';
            kk++;
        }

        seqxA[kk] = seqx[m1[k]];
        seqyA[kk] = seqy[m2[k]];
        Liden += (seqxA[kk] == seqyA[kk]);
        d = sqrt(dist(&xt[m1[k]][0], &ya[m2[k]][0]));
        if (d < d0_out)
            seqM[kk] = ':';
        else
            seqM[kk] = '.';
        do_vec[kk] = d;
        // SO+=(d<3.5);
        kk++;
        i_old = m1[k] + 1;
        j_old = m2[k] + 1;
    }
    // SO/=getmin(xlen,ylen);
    // cout<<n_ali8<<'\t'
    //<<rmsd0<<'\t'
    //<<100.*SO<<endl;

    // tail
    for (int i = i_old; i < xlen; i++)
    {
        // align x to gap
        seqxA[kk] = seqx[i];
        seqyA[kk] = '-';
        seqM[kk] = ' ';
        kk++;
    }
    for (int j = j_old; j < ylen; j++)
    {
        // align y to gap
        seqxA[kk] = '-';
        seqyA[kk] = seqy[j];
        seqM[kk] = ' ';
        kk++;
    }
    seqxA = seqxA.substr(0, kk);
    seqyA = seqyA.substr(0, kk);
    seqM = seqM.substr(0, kk);

    /* free memory */
    clean_up_after_approx_TM(invmap0, invmap, score, path, val,
                             xtm, ytm, xt, r1, r2, xlen, minlen);
    delete[] m1;
    delete[] m2;
    return 0; // zero for no exception
}
/* entry function for TM-align with circular permutation
 * i_opt, a_opt, u_opt, d_opt, TMcut are not implemented yet */
int CPalign_main(double **xa, double **ya,
                 const char *seqx, const char *seqy, const char *secx, const char *secy,
                 double t0[3], double u0[3][3],
                 double &TM1, double &TM2, double &TM3, double &TM4, double &TM5,
                 double &d0_0, double &TM_0,
                 double &d0A, double &d0B, double &d0u, double &d0a, double &d0_out,
                 string &seqM, string &seqxA, string &seqyA, vector<double> &do_vec,
                 double &rmsd0, int &L_ali, double &Liden,
                 double &TM_ali, double &rmsd_ali, int &n_ali, int &n_ali8,
                 const int xlen, const int ylen,
                 const vector<string> sequence, const double Lnorm_ass,
                 const double d0_scale, const int i_opt, const int a_opt,
                 const bool u_opt, const bool d_opt, const bool fast_opt,
                 const int mol_type, const double TMcut = -1)
{
    char *seqx_cp;             // for the protein sequence
    char *secx_cp;             // for the secondary structure
    double **xa_cp;            // coordinates
    string seqxA_cp, seqyA_cp; // alignment
    int i, r;
    int cp_point = 0;    // position of circular permutation
    int cp_aln_best = 0; // amount of aligned residue in sliding window
    int cp_aln_current;  // amount of aligned residue in sliding window

    /* duplicate structure */
    NewArray(&xa_cp, xlen * 2, 3);
    seqx_cp = new char[xlen * 2 + 1];
    secx_cp = new char[xlen * 2 + 1];
    for (r = 0; r < xlen; r++)
    {
        xa_cp[r + xlen][0] = xa_cp[r][0] = xa[r][0];
        xa_cp[r + xlen][1] = xa_cp[r][1] = xa[r][1];
        xa_cp[r + xlen][2] = xa_cp[r][2] = xa[r][2];
        seqx_cp[r + xlen] = seqx_cp[r] = seqx[r];
        secx_cp[r + xlen] = secx_cp[r] = secx[r];
    }
    seqx_cp[2 * xlen] = 0;
    secx_cp[2 * xlen] = 0;

    /* fTM-align alignment */
    double TM1_cp, TM2_cp, TM4_cp;
    const double Lnorm_tmp = getmin(xlen, ylen);
    TMalign_main(xa_cp, ya, seqx_cp, seqy, secx_cp, secy,
                 t0, u0, TM1_cp, TM2_cp, TM3, TM4_cp, TM5,
                 d0_0, TM_0, d0A, d0B, d0u, d0a, d0_out, seqM, seqxA_cp, seqyA_cp,
                 do_vec, rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
                 xlen * 2, ylen, sequence, Lnorm_tmp, d0_scale,
                 0, false, true, false, true, mol_type, -1, 0);

    /* delete gap in seqxA_cp */
    r = 0;
    seqxA = seqxA_cp;
    seqyA = seqyA_cp;
    for (i = 0; i < seqxA_cp.size(); i++)
    {
        if (seqxA_cp[i] != '-')
        {
            seqxA[r] = seqxA_cp[i];
            seqyA[r] = seqyA_cp[i];
            r++;
        }
    }
    seqxA = seqxA.substr(0, r);
    seqyA = seqyA.substr(0, r);

    /* count the number of aligned residues in each window
     * r - residue index in the original unaligned sequence
     * i - position in the alignment */
    for (r = 0; r < xlen - 1; r++)
    {
        cp_aln_current = 0;
        for (i = r; i < r + xlen; i++)
            cp_aln_current += (seqyA[i] != '-');

        if (cp_aln_current > cp_aln_best)
        {
            cp_aln_best = cp_aln_current;
            cp_point = r;
        }
    }
    seqM.clear();
    seqxA.clear();
    seqyA.clear();
    seqxA_cp.clear();
    seqyA_cp.clear();
    rmsd0 = Liden = n_ali = n_ali8 = 0;

    /* fTM-align alignment */
    TMalign_main(xa, ya, seqx, seqy, secx, secy,
                 t0, u0, TM1, TM2, TM3, TM4, TM5,
                 d0_0, TM_0, d0A, d0B, d0u, d0a, d0_out, seqM, seqxA, seqyA,
                 do_vec, rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
                 xlen, ylen, sequence, Lnorm_tmp, d0_scale,
                 0, false, true, false, true, mol_type, -1, 0);

    /* do not use circular permutation of number of aligned residues is not
     * larger than sequence-order dependent alignment */
    // cout<<"cp: aln="<<cp_aln_best<<"\tTM="<<TM4_cp<<endl;
    // cout<<"TM: aln="<<n_ali8<<"\tTM="<<TM4<<endl;
    if (n_ali8 >= cp_aln_best || TM4 >= TM4_cp)
        cp_point = 0;

    /* prepare structure for final alignment */
    seqM.clear();
    seqxA.clear();
    seqyA.clear();
    rmsd0 = Liden = n_ali = n_ali8 = 0;
    if (cp_point != 0)
    {
        for (r = 0; r < xlen; r++)
        {
            xa_cp[r][0] = xa_cp[r + cp_point][0];
            xa_cp[r][1] = xa_cp[r + cp_point][1];
            xa_cp[r][2] = xa_cp[r + cp_point][2];
            seqx_cp[r] = seqx_cp[r + cp_point];
            secx_cp[r] = secx_cp[r + cp_point];
        }
    }
    seqx_cp[xlen] = 0;
    secx_cp[xlen] = 0;

    /* test another round of alignment as concatenated alignment can
     * inflate the number of aligned residues and TM-score. e.g. 1yadA 2duaA */
    if (cp_point != 0)
    {
        TMalign_main(xa_cp, ya, seqx_cp, seqy, secx_cp, secy,
                     t0, u0, TM1_cp, TM2_cp, TM3, TM4_cp, TM5,
                     d0_0, TM_0, d0A, d0B, d0u, d0a, d0_out, seqM, seqxA_cp, seqyA_cp,
                     do_vec, rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, cp_aln_best,
                     xlen, ylen, sequence, Lnorm_tmp, d0_scale,
                     0, false, true, false, true, mol_type, -1, 0);
        // cout<<"cp: aln="<<cp_aln_best<<"\tTM="<<TM4_cp<<endl;
        if (n_ali8 >= cp_aln_best || TM4 >= TM4_cp)
        {
            cp_point = 0;
            for (r = 0; r < xlen; r++)
            {
                xa_cp[r][0] = xa[r][0];
                xa_cp[r][1] = xa[r][1];
                xa_cp[r][2] = xa[r][2];
                seqx_cp[r] = seqx[r];
                secx_cp[r] = secx[r];
            }
        }
    }

    /* full TM-align */
    TMalign_main(xa_cp, ya, seqx_cp, seqy, secx_cp, secy,
                 t0, u0, TM1, TM2, TM3, TM4, TM5,
                 d0_0, TM_0, d0A, d0B, d0u, d0a, d0_out, seqM, seqxA_cp, seqyA_cp,
                 do_vec, rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
                 xlen, ylen, sequence, Lnorm_ass, d0_scale,
                 i_opt, a_opt, u_opt, d_opt, fast_opt, mol_type, TMcut, 0);

    /* correct alignment
     * r - residue index in the original unaligned sequence
     * i - position in the alignment */
    if (cp_point > 0)
    {
        r = 0;
        for (i = 0; i < seqxA_cp.size(); i++)
        {
            r += (seqxA_cp[i] != '-');
            if (r >= (xlen - cp_point))
            {
                i++;
                break;
            }
        }
        seqxA = seqxA_cp.substr(0, i) + '*' + seqxA_cp.substr(i);
        seqM = seqM.substr(0, i) + ' ' + seqM.substr(i);
        seqyA = seqyA_cp.substr(0, i) + '-' + seqyA_cp.substr(i);
    }
    else
    {
        seqxA = seqxA_cp;
        seqyA = seqyA_cp;
    }

    /* clean up */
    delete[] seqx_cp;
    delete[] secx_cp;
    DeleteArray(&xa_cp, xlen * 2);
    seqxA_cp.clear();
    seqyA_cp.clear();
    return cp_point;
}

bool output_cp(const string &xname, const string &yname,
               const string &seqxA, const string &seqyA, const int outfmt_opt,
               int &left_num, int &right_num, int &left_aln_num, int &right_aln_num)
{
    int r;
    bool after_cp = false;
    for (r = 0; r < seqxA.size(); r++)
    {
        if (seqxA[r] == '*')
            after_cp = true;
        else
        {
            if (after_cp)
            {
                right_aln_num++;
                right_num += (seqxA[r] != '-');
            }
            else
            {
                left_aln_num++;
                left_num += (seqxA[r] != '-');
            }
        }
    }
    if (after_cp == false)
    {
        if (outfmt_opt <= 0)
            cout << "No CP" << endl;
        else if (outfmt_opt == 1)
            cout << "#No CP" << endl;
        else if (outfmt_opt == 2)
            cout << "@" << xname << '\t' << yname << '\t' << "No CP" << endl;
    }
    else
    {
        if (outfmt_opt <= 0)
            cout << "CP point in structure_1 alignment: " << left_aln_num << '/' << right_aln_num << '\n'
                 << "CP point in structure_1: " << left_num << '/' << right_num << endl;
        else if (outfmt_opt == 1)
            cout << "#CP_in_aln=" << left_aln_num << '/' << right_aln_num
                 << "\tCP_in_seq=" << left_num << '/' << right_num << endl;
        else if (outfmt_opt == 2)
            cout << "@" << xname << '\t' << yname << '\t' << left_aln_num
                 << '/' << right_aln_num << '\t' << left_num << '/' << right_num << endl;
    }
    return after_cp;
}
#endif
// ===== END TMalign.h =====

/* entry function for se
 * outfmt_opt>=2 should not parse sequence alignment 
 * u_opt corresponds to option -L
 *       if u_opt==2, use d0 from Lnorm_ass for alignment
 * if hinge>0, append to original invmap */
int se_main(
    double **xa, double **ya, const char *seqx, const char *seqy,
    double &TM1, double &TM2, double &TM3, double &TM4, double &TM5,
    double &d0_0, double &TM_0,
    double &d0A, double &d0B, double &d0u, double &d0a, double &d0_out,
    string &seqM, string &seqxA, string &seqyA, vector<double> &do_vec,
    double &rmsd0, int &L_ali, double &Liden,
    double &TM_ali, double &rmsd_ali, int &n_ali, int &n_ali8,
    const int xlen, const int ylen, const vector<string> &sequence,
    const double Lnorm_ass, const double d0_scale, const bool i_opt,
    const bool a_opt, const int u_opt, const bool d_opt, const int mol_type,
    const int outfmt_opt, int *invmap, const int hinge=0)
{
    double D0_MIN;        //for d0
    double Lnorm;         //normalization length
    double score_d8,d0,d0_search,dcu0;//for TMscore search
    double **score;       // Input score table for dynamic programming
    bool   **path;        // for dynamic programming  
    double **val;         // for dynamic programming  

    int *m1=NULL;
    int *m2=NULL;
    double d;
    if (outfmt_opt<2)
    {
        m1=new int[xlen]; //alignd index in x
        m2=new int[ylen]; //alignd index in y
    }

    /***********************/
    /* allocate memory     */
    /***********************/
    NewArray(&score, xlen+1, ylen+1);
    NewArray(&path, xlen+1, ylen+1);
    NewArray(&val, xlen+1, ylen+1);
    int *invmap0          = new int[ylen+1];
    int i,j;
    if (hinge==0) for (j=0;j<=ylen;j++) invmap0[j]=-1;
    else for (j=0;j<ylen;j++) invmap0[j]=invmap[j];
    vector<char> seqM_char;
    if (hinge)
    {
        seqM_char.assign(ylen,hinge+'0');
        j=-1;
        for (int r=0;r<seqM.size();r++)
        {
            j+=seqyA[r]!='-';
            if (seqM[r]!=' ') seqM_char[j]=seqM[r];
        }
    }

    /* set d0 */
    parameter_set4search(xlen, ylen, D0_MIN, Lnorm,
        score_d8, d0, d0_search, dcu0); // set score_d8
    parameter_set4final(xlen, D0_MIN, Lnorm,
        d0B, d0_search, mol_type); // set d0B
    parameter_set4final(ylen, D0_MIN, Lnorm,
        d0A, d0_search, mol_type); // set d0A
    if (a_opt)
        parameter_set4final((xlen+ylen)*0.5, D0_MIN, Lnorm,
            d0a, d0_search, mol_type); // set d0a
    if (u_opt)
    {
        parameter_set4final(Lnorm_ass, D0_MIN, Lnorm,
            d0u, d0_search, mol_type); // set d0u
        if (u_opt==2)
        {
            parameter_set4search(Lnorm_ass, Lnorm_ass, D0_MIN, Lnorm,
                score_d8, d0, d0_search, dcu0); // set score_d8
        }
    }

    /* perform alignment */
    if (hinge==0) for(j=0; j<ylen; j++) invmap[j]=-1;
    if (!i_opt) NWDP_SE(path, val, xa, ya, xlen, ylen, d0*d0, 0, invmap, hinge);
    else
    {
        int i1 = -1;// in C version, index starts from zero, not from one
        int i2 = -1;
        int L1 = sequence[0].size();
        int L2 = sequence[1].size();
        int L = min(L1, L2);// Get positions for aligned residues
        for (int kk1 = 0; kk1 < L; kk1++)
        {
            if (sequence[0][kk1] != '-') i1++;
            if (sequence[1][kk1] != '-')
            {
                i2++;
                if (i2 >= ylen || i1 >= xlen) kk1 = L;
                else if (sequence[0][kk1] != '-') invmap[i2] = i1;
            }
        }
    }
    
    if (hinge==0) rmsd0=TM1=TM2=TM3=TM4=TM5=0;
    else
    {
        TM2*=xlen;
        TM1*=ylen;
        TM3*=(xlen+ylen)*0.5;
        TM4*=Lnorm_ass;
        TM5*=ylen;
        rmsd0=rmsd0*rmsd0*n_ali8;
    }
    int k=0;
    n_ali=0;
    n_ali8=0;
    for(int i=0,j=0; j<ylen; j++)
    {
        i=invmap[j];
        if(i>=0)//aligned
        {
            n_ali++;
            d=sqrt(dist(&xa[i][0], &ya[j][0]));
            if (d <= score_d8 || i_opt || invmap0[j]==i)
            {
                if (outfmt_opt<2)
                {
                    m1[k]=i;
                    m2[k]=j;
                }
                k++;
                if (invmap0[j]==i) continue;
                TM2+=1/(1+(d/d0B)*(d/d0B)); // chain_1
                TM1+=1/(1+(d/d0A)*(d/d0A)); // chain_2
                if (a_opt) TM3+=1/(1+(d/d0a)*(d/d0a)); // -a
                if (u_opt) TM4+=1/(1+(d/d0u)*(d/d0u)); // -u
                if (d_opt) TM5+=1/(1+(d/d0_scale)*(d/d0_scale)); // -d
                rmsd0+=d*d;
            }
            else if (hinge) invmap[j]=-1;
        }
    }
    n_ali8=k;
    TM2/=xlen;
    TM1/=ylen;
    TM3/=(xlen+ylen)*0.5;
    TM4/=Lnorm_ass;
    TM5/=ylen;
    if (n_ali8) rmsd0=sqrt(rmsd0/n_ali8);

    if (outfmt_opt>=2)
    {
        if (hinge) seqM_char.clear();    
        delete []invmap0;
        DeleteArray(&score, xlen+1);
        DeleteArray(&path, xlen+1);
        DeleteArray(&val, xlen+1);
        return 0;
    }

    /* extract aligned sequence */
    int ali_len=xlen+ylen; //maximum length of alignment
    seqxA.assign(ali_len,'-');
    seqM.assign( ali_len,' ');
    seqyA.assign(ali_len,'-');
    do_vec.clear();
    do_vec.assign(ali_len,0);
    
    int kk=0, i_old=0, j_old=0;
    d=0;
    Liden=0;
    for(int k=0; k<n_ali8; k++)
    {
        for(int i=i_old; i<m1[k]; i++)
        {
            //align x to gap
            seqxA[kk]=seqx[i];
            seqyA[kk]='-';
            seqM[kk]=' ';                    
            kk++;
        }

        for(int j=j_old; j<m2[k]; j++)
        {
            //align y to gap
            seqxA[kk]='-';
            seqyA[kk]=seqy[j];
            seqM[kk]=' ';
            kk++;
        }

        seqxA[kk]=seqx[m1[k]];
        seqyA[kk]=seqy[m2[k]];
        Liden+=(seqxA[kk]==seqyA[kk]);
        d=sqrt(dist(&xa[m1[k]][0], &ya[m2[k]][0]));
        if(d<d0_out) seqM[kk]=':';
        else         seqM[kk]='.';
        do_vec[kk]=d;
        kk++;  
        i_old=m1[k]+1;
        j_old=m2[k]+1;
    }

    //tail
    for(int i=i_old; i<xlen; i++)
    {
        //align x to gap
        seqxA[kk]=seqx[i];
        seqyA[kk]='-';
        seqM[kk]=' ';
        kk++;
    }    
    for(int j=j_old; j<ylen; j++)
    {
        //align y to gap
        seqxA[kk]='-';
        seqyA[kk]=seqy[j];
        seqM[kk]=' ';
        kk++;
    }
    seqxA=seqxA.substr(0,kk);
    seqyA=seqyA.substr(0,kk);
    seqM =seqM.substr(0,kk);
    if (hinge)
    {
        j=-1;
        for (int r=0;r<seqM.size();r++)
        {
            j+=seqyA[r]!='-';
            if (seqM[r]!=' ') seqM[r]=seqM_char[j];
        }
    }

    /* free memory */
    delete [] invmap0;
    delete [] m1;
    delete [] m2;
    DeleteArray(&score, xlen+1);
    DeleteArray(&path, xlen+1);
    DeleteArray(&val, xlen+1);
    return 0; // zero for no exception
}

// ===== END se.h =====
// Original: #include "flexalign.h"
// ===== BEGIN flexalign.h =====
/* Functions for the core TMalign algorithm, including the entry function
 * flexalign_main */
#ifndef flexalign_h
#define flexalign_h 1

// Original: #include "TMalign.h"
// Already expanded: TMalign.h

void t_u2tu(double t0[3], double u0[3][3], vector<double> &tu_tmp)
{
    int i, j, k;
    for (i = 0; i < 3; i++)
        tu_tmp[i] = t0[i];
    k = 3;
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
        {
            tu_tmp[k] = u0[i][j];
            k++;
        }
}

void tu2t_u(vector<double> tu_tmp, double t0[3], double u0[3][3])
{
    int i, j, k;
    for (i = 0; i < 3; i++)
        t0[i] = tu_tmp[i];
    k = 3;
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
        {
            u0[i][j] = tu_tmp[k];
            k++;
        }
}

void aln2invmap(const string &seqxA, const string &seqyA, int *invmap)
{
    int i, j, r;
    int ylen = 0;
    for (r = 0; r < seqyA.size(); r++)
        ylen += seqyA[r] != '-';
    for (j = 0; j < ylen; j++)
        invmap[j] = -1;

    i = j = -1;
    for (r = 0; r < seqxA.size(); r++)
    {
        i += seqxA[r] != '-';
        j += seqyA[r] != '-';
        if (seqxA[r] != '-' && seqyA[r] != '-')
            invmap[j] = i;
    }
}

int flexalign_main(double **xa, double **ya,
                   const char *seqx, const char *seqy, const char *secx, const char *secy,
                   double t0[3], double u0[3][3], vector<vector<double> > &tu_vec,
                   double &TM1, double &TM2, double &TM3, double &TM4, double &TM5,
                   double &d0_0, double &TM_0,
                   double &d0A, double &d0B, double &d0u, double &d0a, double &d0_out,
                   string &seqM, string &seqxA, string &seqyA, vector<double> &do_vec,
                   double &rmsd0, int &L_ali, double &Liden,
                   double &TM_ali, double &rmsd_ali, int &n_ali, int &n_ali8,
                   const int xlen, const int ylen,
                   const vector<string> sequence, const double Lnorm_ass,
                   const double d0_scale, const int i_opt, const int a_opt,
                   const bool u_opt, const bool d_opt, const bool fast_opt,
                   const int mol_type, const int hinge_opt, const int ss_opt)
{
    vector<double> tu_tmp(12, 0);
    int round2 = tu_vec.size();
    if (round2 == 0)
    {
        TMalign_main(xa, ya, seqx, seqy, secx, secy, t0, u0,
                     TM1, TM2, TM3, TM4, TM5, d0_0, TM_0,
                     d0A, d0B, d0u, d0a, d0_out, seqM, seqxA, seqyA, do_vec,
                     rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
                     xlen, ylen, sequence, Lnorm_ass,
                     d0_scale, i_opt, a_opt, u_opt, d_opt, fast_opt, mol_type, -1, ss_opt);

        t_u2tu(t0, u0, tu_tmp);
        tu_vec.push_back(tu_tmp);
    }

    int i, j, r;
    int *invmap = new int[ylen + 1];
    for (j = 0; j < ylen + 1; j++)
        invmap[j] = -1;
    double **xt;
    NewArray(&xt, xlen, 3);
    do_rotation(xa, xt, xlen, t0, u0);

    TM1 = TM2 = TM3 = TM4 = TM5 = rmsd0 = 0;
    seqM = "";
    seqxA = "";
    seqyA = "";
    n_ali = n_ali8 = 0;
    se_main(xt, ya, seqx, seqy, TM1, TM2, TM3, TM4, TM5, d0_0, TM_0,
            d0A, d0B, d0u, d0a, d0_out, seqM, seqxA, seqyA, do_vec,
            rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
            xlen, ylen, sequence, Lnorm_ass, d0_scale, i_opt,
            a_opt, u_opt, d_opt, mol_type, 0, invmap, 1);
    if (round2)
    {
        /* aligned structure A vs unaligned structure B */
        int xlen_h = n_ali8;
        int ylen_h = ylen - n_ali8;
        char *seqx_h = new char[xlen + 1];
        char *seqy_h = new char[ylen + 1];
        char *secx_h = new char[xlen + 1];
        char *secy_h = new char[ylen + 1];
        seqx_h[xlen] = seqy_h[ylen] = 0;
        secx_h[xlen] = secy_h[ylen] = 0;
        double **xa_h, **ya_h;
        NewArray(&xa_h, xlen, 3);
        NewArray(&ya_h, ylen, 3);

        int r1, r2;
        i = j = -1;
        r1 = r2 = 0;
        for (r = 0; r < seqxA.size(); r++)
        {
            i += (seqxA[r] != '-');
            j += (seqyA[r] != '-');
            if (seqxA[r] != '-' && seqyA[r] != '-')
            {
                seqx_h[r1] = seqx[i];
                secx_h[r1] = secx[i];
                xa_h[r1][0] = xa[i][0];
                xa_h[r1][1] = xa[i][1];
                xa_h[r1][2] = xa[i][2];
                r1++;
            }
            else if (seqxA[r] == '-')
            {
                seqy_h[r2] = seqy[j];
                secy_h[r2] = secy[j];
                ya_h[r2][0] = ya[j][0];
                ya_h[r2][1] = ya[j][1];
                ya_h[r2][2] = ya[j][2];
                r2++;
            }
        }

        double TM1_h, TM2_h;
        double TM3_h, TM4_h, TM5_h; // for a_opt, u_opt, d_opt
        double d0_0_h, TM_0_h;
        double d0A_h, d0B_h, d0u_h, d0a_h;
        double d0_out_h = 5.0;
        string seqM_h, seqxA_h, seqyA_h; // for output alignment
        double rmsd0_h = 0.0;
        int L_ali_h = 0; // Aligned length in standard_TMscore
        double Liden_h = 0;
        double TM_ali_h, rmsd_ali_h; // TMscore and rmsd in standard_TMscore
        int n_ali_h = 0;
        int n_ali8_h = 0;

        TMalign_main(xa_h, ya_h, seqx_h, seqy_h, secx_h, secy_h, t0, u0,
                     TM1_h, TM2_h, TM3_h, TM4_h, TM5_h, d0_0_h, TM_0_h, d0A_h, d0B_h,
                     d0u_h, d0a_h, d0_out_h, seqM_h, seqxA_h, seqyA_h, do_vec,
                     rmsd0_h, L_ali_h, Liden_h, TM_ali_h, rmsd_ali_h, n_ali_h, n_ali8_h,
                     xlen_h, ylen_h, sequence, Lnorm_ass,
                     d0_scale, i_opt, a_opt, u_opt, d_opt, fast_opt, mol_type, -1, ss_opt);

        do_rotation(xa, xt, xlen, t0, u0);
        t_u2tu(t0, u0, tu_vec[0]);

        int *invmap_h = new int[ylen + 1];
        for (j = 0; j < ylen + 1; j++)
            invmap_h[j] = -1;
        TM1_h = TM2_h = TM3_h = TM4_h = TM5_h = rmsd0_h = 0;
        seqM_h = "";
        seqxA_h = "";
        seqyA_h = "";
        n_ali_h = n_ali8_h = 0;
        se_main(xt, ya, seqx, seqy, TM1_h, TM2_h, TM3_h, TM4_h, TM5_h, d0_0,
                TM_0, d0A, d0B, d0u, d0a, d0_out, seqM_h, seqxA_h, seqyA_h, do_vec,
                rmsd0_h, L_ali, Liden, TM_ali, rmsd_ali, n_ali_h, n_ali8_h,
                xlen, ylen, sequence, Lnorm_ass, d0_scale, i_opt,
                a_opt, u_opt, d_opt, mol_type, 0, invmap_h, 1);

        /* unaligned structure A vs aligned structure B */
        xlen_h = xlen - n_ali8;
        ylen_h = n_ali8;

        i = j = -1;
        r1 = r2 = 0;
        for (r = 0; r < seqxA.size(); r++)
        {
            i += (seqxA[r] != '-');
            j += (seqyA[r] != '-');
            if (seqyA[r] == '-')
            {
                seqx_h[r1] = seqx[i];
                secx_h[r1] = secx[i];
                xa_h[r1][0] = xa[i][0];
                xa_h[r1][1] = xa[i][1];
                xa_h[r1][2] = xa[i][2];
                r1++;
            }
            else if (seqxA[r] != '-' && seqyA[r] != '-')
            {
                seqy_h[r2] = seqy[j];
                secy_h[r2] = secy[j];
                ya_h[r2][0] = ya[j][0];
                ya_h[r2][1] = ya[j][1];
                ya_h[r2][2] = ya[j][2];
                r2++;
            }
        }

        d0_out_h = 5.0;
        L_ali_h = Liden_h = 0;
        TM1 = TM2 = TM3 = TM4 = TM5 = rmsd0 = 0;
        seqM = "";
        seqxA = "";
        seqyA = "";
        n_ali = n_ali8 = 0;

        TMalign_main(xa_h, ya_h, seqx_h, seqy_h, secx_h, secy_h, t0, u0,
                     TM1, TM2, TM3, TM4, TM5, d0_0_h, TM_0_h, d0A_h, d0B_h,
                     d0u_h, d0a_h, d0_out_h, seqM, seqxA, seqyA, do_vec,
                     rmsd0, L_ali_h, Liden_h, TM_ali_h, rmsd_ali_h, n_ali, n_ali8,
                     xlen_h, ylen_h, sequence, Lnorm_ass,
                     d0_scale, i_opt, a_opt, u_opt, d_opt, fast_opt, mol_type, -1, ss_opt);

        do_rotation(xa, xt, xlen, t0, u0);

        for (j = 0; j < ylen + 1; j++)
            invmap[j] = -1;
        TM1 = TM2 = TM3 = TM4 = TM5 = rmsd0 = 0;
        seqM = "";
        seqxA = "";
        seqyA = "";
        n_ali = n_ali8 = 0;
        se_main(xt, ya, seqx, seqy, TM1, TM2, TM3, TM4, TM5, d0_0,
                TM_0, d0A, d0B, d0u, d0a, d0_out, seqM, seqxA, seqyA, do_vec,
                rmsd0, L_ali, Liden, TM_ali, rmsd_ali, n_ali, n_ali8,
                xlen, ylen, sequence, Lnorm_ass, d0_scale, i_opt,
                a_opt, u_opt, d_opt, mol_type, 0, invmap, 1);

        double TM_h = (TM1_h > TM2_h) ? TM1_h : TM2_h;
        double TM = (TM1 > TM2) ? TM1 : TM2;
        if (TM_h > TM)
        {
            TM1 = TM1_h;
            TM2 = TM2_h;
            TM3 = TM3_h;
            TM4 = TM4_h;
            TM5 = TM5_h;
            seqM = seqM_h;
            seqxA = seqxA_h;
            seqyA = seqyA_h;
            rmsd0 = rmsd0_h;
            n_ali = n_ali_h;
            n_ali8 = n_ali8_h;
            for (j = 0; j < ylen + 1; j++)
                invmap[j] = invmap_h[j];
        }
        else
            t_u2tu(t0, u0, tu_vec[0]);

        /* clean up */
        delete[] invmap_h;
        DeleteArray(&xa_h, xlen);
        DeleteArray(&ya_h, ylen);
        seqM_h.clear();
        seqxA_h.clear();
        seqyA_h.clear();
        delete[] seqx_h;
        delete[] secx_h;
        delete[] seqy_h;
        delete[] secy_h;
    }
    for (r = 0; r < seqM.size(); r++)
        if (seqM[r] == '1')
            seqM[r] = '0';

    int minlen = min(xlen, ylen);
    int hinge;
    for (hinge = 0; hinge < hinge_opt; hinge++)
    {
        if (minlen - n_ali8 < 5)
            break;
        int xlen_h = xlen - n_ali8;
        int ylen_h = ylen - n_ali8;
        char *seqx_h = new char[xlen_h + 1];
        char *seqy_h = new char[ylen_h + 1];
        char *secx_h = new char[xlen_h + 1];
        char *secy_h = new char[ylen_h + 1];
        seqx_h[xlen_h] = seqy_h[ylen_h] = 0;
        secx_h[xlen_h] = secy_h[ylen_h] = 0;
        double **xa_h, **ya_h;
        NewArray(&xa_h, xlen_h, 3);
        NewArray(&ya_h, ylen_h, 3);
        vector<int> r1toi(xlen_h, 0);
        vector<int> r2toj(ylen_h, 0);

        int r1, r2;
        i = j = -1;
        r1 = r2 = 0;
        for (r = 0; r < seqxA.size(); r++)
        {
            i += (seqxA[r] != '-');
            j += (seqyA[r] != '-');
            if (seqyA[r] == '-')
            {
                seqx_h[r1] = seqx[i];
                secx_h[r1] = secx[i];
                xa_h[r1][0] = xa[i][0];
                xa_h[r1][1] = xa[i][1];
                xa_h[r1][2] = xa[i][2];
                r1toi[r1] = i;
                r1++;
            }
            if (seqxA[r] == '-')
            {
                seqy_h[r2] = seqy[j];
                secy_h[r2] = secy[j];
                ya_h[r2][0] = ya[j][0];
                ya_h[r2][1] = ya[j][1];
                ya_h[r2][2] = ya[j][2];
                r2toj[r2] = j;
                r2++;
            }
        }

        double TM1_h, TM2_h;
        double TM3_h, TM4_h, TM5_h; // for a_opt, u_opt, d_opt
        double d0_0_h, TM_0_h;
        double d0A_h, d0B_h, d0u_h, d0a_h;
        double d0_out_h = 5.0;
        string seqM_h, seqxA_h, seqyA_h; // for output alignment
        double rmsd0_h = 0.0;
        int L_ali_h = 0; // Aligned length in standard_TMscore
        double Liden_h = 0;
        double TM_ali_h, rmsd_ali_h; // TMscore and rmsd in standard_TMscore
        int n_ali_h = 0;
        int n_ali8_h = 0;

        TMalign_main(xa_h, ya_h, seqx_h, seqy_h, secx_h, secy_h, t0, u0,
                     TM1_h, TM2_h, TM3_h, TM4_h, TM5_h, d0_0_h, TM_0_h, d0A_h, d0B_h,
                     d0u_h, d0a_h, d0_out_h, seqM_h, seqxA_h, seqyA_h, do_vec,
                     rmsd0_h, L_ali_h, Liden_h, TM_ali_h, rmsd_ali_h, n_ali_h, n_ali8_h,
                     xlen_h, ylen_h, sequence, Lnorm_ass,
                     d0_scale, i_opt, a_opt, u_opt, d_opt, fast_opt, mol_type, -1, ss_opt);

        do_rotation(xa, xt, xlen, t0, u0);

        TM1_h = TM1;
        TM2_h = TM2;
        TM3_h = TM3;
        TM4_h = TM4;
        TM5_h = TM5;
        seqM_h = seqM;
        seqxA_h = seqxA;
        seqyA_h = seqyA;
        rmsd0_h = rmsd0;
        n_ali_h = n_ali;
        n_ali8_h = n_ali8;
        int *invmap_h = new int[ylen + 1];
        for (j = 0; j < ylen + 1; j++)
            invmap_h[j] = invmap[j];
        se_main(xt, ya, seqx, seqy, TM1_h, TM2_h, TM3_h, TM4_h, TM5_h, d0_0, TM_0,
                d0A, d0B, d0u, d0a, d0_out, seqM_h, seqxA_h, seqyA_h, do_vec,
                rmsd0_h, L_ali, Liden, TM_ali, rmsd_ali, n_ali_h, n_ali8_h,
                xlen, ylen, sequence, Lnorm_ass, d0_scale, i_opt,
                a_opt, u_opt, d_opt, mol_type, 0, invmap_h, hinge + 1);
        int new_ali = 0;
        for (r = 0; r < seqM_h.size(); r++)
            new_ali += (seqM_h[r] == hinge + '1');
        if (n_ali8_h - n_ali8 < 5)
            new_ali = 0;
        if (new_ali >= 5)
        {
            TM1 = TM1_h;
            TM2 = TM2_h;
            TM3 = TM3_h;
            TM4 = TM4_h;
            TM5 = TM5_h;
            seqM = seqM_h;
            seqxA = seqxA_h;
            seqyA = seqyA_h;
            rmsd0 = rmsd0_h;
            n_ali = n_ali_h;
            n_ali8 = n_ali8_h;
            t_u2tu(t0, u0, tu_tmp);
            tu_vec.push_back(tu_tmp);
            for (j = 0; j < ylen + 1; j++)
                invmap[j] = invmap_h[j];
            // cout<<">hinge="<<hinge<<'\n'
            //<<seqxA<<'\n'<<seqM<<'\n'<<seqyA<<endl;
            // for (j=0;j<ylen;j++) if ((i=invmap[j])>=0) cout<<"("<<i<<","<<j<<")";
            // cout<<endl;
        }

        /* clean up */
        delete[] invmap_h;
        DeleteArray(&xa_h, xlen_h);
        DeleteArray(&ya_h, ylen_h);
        r1toi.clear();
        r2toj.clear();
        seqM_h.clear();
        seqxA_h.clear();
        seqyA_h.clear();
        delete[] seqx_h;
        delete[] secx_h;
        delete[] seqy_h;
        delete[] secy_h;
        if (new_ali < 5)
            break;
    }

    if (tu_vec.size() <= 1)
    {
        DeleteArray(&xt, xlen);
        delete[] invmap;
        return tu_vec.size();
    }

    /* re-derive alignment based on tu_vec */
    vector<char> seqM_char(ylen, ' ');
    vector<double> di_vec(ylen, -1);
    double d;
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        tu2t_u(tu_vec[hinge], t0, u0);
        do_rotation(xa, xt, xlen, t0, u0);
        for (j = 0; j < ylen; j++)
        {
            i = invmap[j];
            if (i < 0)
                continue;
            d = sqrt(dist(xt[i], ya[j]));
            if (di_vec[j] < 0 || d <= di_vec[j])
            {
                di_vec[j] = d;
                seqM_char[j] = hinge + '0';
            }
        }
    }
    j = -1;
    for (r = 0; r < seqM.size(); r++)
    {
        if (seqyA[r] == '-')
            continue;
        j++;
        seqM[r] = seqM_char[j];
    }

    /* smooth out AFP assignment: remove singleton insert */
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        j = -1;
        for (r = 0; r < seqM.size(); r++)
        {
            if (seqyA[r] == '-')
                continue;
            j++;
            if (seqM_char[j] != hinge + '0')
                continue;
            if (r < seqM.size() - 1 && (seqM[r + 1] == hinge + '0' || seqM[r + 1] == ' '))
                continue;
            if (r > 0 && (seqM[r - 1] == hinge + '0' || seqM[r - 1] == ' '))
                continue;
            if (r < seqM.size() - 1 && r > 0 && seqM[r - 1] != seqM[r + 1])
                continue;
            if (r > 0)
                seqM[r] = seqM_char[j] = seqM[r - 1];
            else
                seqM[r] = seqM_char[j] = seqM[r + 1];
        }
    }
    /* smooth out AFP assignment: remove singleton at the end of fragment */
    char left_hinge = ' ';
    char right_hinge = ' ';
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        j = -1;
        for (r = 0; r < seqM.size(); r++)
        {
            if (seqyA[r] == '-')
                continue;
            j++;
            if (seqM[r] != hinge + '0')
                continue;
            if (r > 0 && seqM[r - 1] == ' ' && r < seqM.size() - 1 && seqM[r + 1] == ' ')
                continue;

            left_hinge = ' ';
            for (i = r - 1; i >= 0; i--)
            {
                if (seqM[i] == ' ')
                    continue;
                left_hinge = seqM[i];
                break;
            }
            if (left_hinge == hinge + '0')
                continue;

            right_hinge = ' ';
            for (i = r + 1; i < seqM.size(); i++)
            {
                if (seqM[i] == ' ')
                    continue;
                right_hinge = seqM[i];
                break;
            }
            if (right_hinge == hinge + '0')
                continue;
            if (left_hinge != right_hinge && left_hinge != ' ' && right_hinge != ' ')
                continue;

            if (right_hinge != ' ')
                seqM[r] = seqM_char[j] = right_hinge;
            else if (left_hinge != ' ')
                seqM[r] = seqM_char[j] = left_hinge;
        }
    }
    /* smooth out AFP assignment: remove dimer insert */
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        j = -1;
        for (r = 0; r < seqM.size() - 1; r++)
        {
            if (seqyA[r] == '-')
                continue;
            j++;
            if (seqM[r] != hinge + '0' || seqM[r + 1] != hinge + '0')
                continue;

            if (r < seqM.size() - 2 && (seqM[r + 2] == ' ' || seqM[r + 2] == hinge + '0'))
                continue;
            if (r > 0 && (seqM[r - 1] == ' ' || seqM[r - 1] == hinge + '0'))
                continue;
            if (r < seqM.size() - 2 && r > 0 && seqM[r - 1] != seqM[r + 2])
                continue;

            if (r > 0)
                seqM[r] = seqM_char[j] = seqM[r + 1] = seqM_char[j + 1] = seqM[r - 1];
            else
                seqM[r] = seqM_char[j] = seqM[r + 1] = seqM_char[j + 1] = seqM[r + 2];
        }
    }
    /* smooth out AFP assignment: remove disconnected singleton */
    int i1, i2;
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        j = -1;
        for (r = 0; r < seqM.size(); r++)
        {
            if (seqyA[r] == '-')
                continue;
            j++;
            if (seqM[r] != hinge + '0')
                continue;

            left_hinge = ' ';
            for (i = r - 1; i >= 0; i--)
            {
                if (seqM[i] == ' ')
                    continue;
                left_hinge = seqM[i];
                i1 = (r - i);
                break;
            }
            if (left_hinge == hinge + '0')
                continue;

            right_hinge = ' ';
            for (i = r + 1; i < seqM.size(); i++)
            {
                if (seqM[i] == ' ')
                    continue;
                right_hinge = seqM[i];
                i2 = (i - r);
                break;
            }
            if (right_hinge == hinge + '0')
                continue;

            if (right_hinge == ' ')
                seqM[r] = seqM_char[j] = left_hinge;
            else if (left_hinge == ' ')
                seqM[r] = seqM_char[j] = right_hinge;
            else
            {
                if (i1 < i2)
                    seqM[r] = seqM_char[j] = left_hinge;
                else
                    seqM[r] = seqM_char[j] = right_hinge;
            }
        }
    }

    /* recalculate all scores */
    for (hinge = tu_vec.size() - 1; hinge >= 0; hinge--)
    {
        tu2t_u(tu_vec[hinge], t0, u0);
        do_rotation(xa, xt, xlen, t0, u0);
        for (j = 0; j < ylen; j++)
        {
            i = invmap[j];
            if (i < 0)
                continue;
            if (seqM_char[j] != hinge + '0')
                continue;
            d = sqrt(dist(xt[i], ya[j]));
            if (di_vec[j] < 0 || d <= di_vec[j])
            {
                di_vec[j] = d;
                seqM_char[j] = hinge + '0';
            }
        }
    }
    rmsd0 = TM1 = TM2 = TM3 = TM4 = TM5 = 0;
    Liden = 0;
    for (r = 0; r < seqM.size(); r++)
        if (seqM[r] != ' ')
            Liden += seqxA[r] == seqyA[r];
    for (j = 0; j < ylen; j++)
    {
        i = invmap[j];
        if (i < 0)
            continue;
        {
            d = di_vec[j];
            TM2 += 1 / (1 + (d / d0B) * (d / d0B)); // chain_1
            TM1 += 1 / (1 + (d / d0A) * (d / d0A)); // chain_2
            if (a_opt)
                TM3 += 1 / (1 + (d / d0a) * (d / d0a)); // -a
            if (u_opt)
                TM4 += 1 / (1 + (d / d0u) * (d / d0u)); // -u
            if (d_opt)
                TM5 += 1 / (1 + (d / d0_scale) * (d / d0_scale)); // -d
            rmsd0 += d * d;
        }
    }
    TM2 /= xlen;
    TM1 /= ylen;
    TM3 /= (xlen + ylen) * 0.5;
    TM4 /= Lnorm_ass;
    TM5 /= ylen;
    if (n_ali8)
        rmsd0 = sqrt(rmsd0 / n_ali8);
    for (hinge = tu_vec.size() - 1; hinge > 0; hinge--)
    {
        int afp_len = 0;
        for (r = 0; r < seqM.size(); r++)
            afp_len += seqM[r] == hinge + '0';
        if (afp_len)
            break;
        tu_vec.pop_back(); // remove unnecessary afp
    }

    /* clean up */
    seqM_char.clear();
    di_vec.clear();
    DeleteArray(&xt, xlen);
    delete[] invmap;
    return tu_vec.size();
}

/* extract rotation matrix based on TMscore8 */
void output_flexalign_rotation_matrix(const char *fname_matrix,
                                      const vector<vector<double> > &tu_vec, double t[3], double u[3][3])
{
    stringstream ss;
    char dest[1000];
    for (int hinge = 0; hinge < tu_vec.size(); hinge++)
    {
        tu2t_u(tu_vec[hinge], t, u);
        ss << "------ The rotation matrix to rotate Structure_1 to Structure_2 ------\n";
        sprintf(dest, "m %18s %14s %14s %14s\n", "t[m]", "u[m][0]", "u[m][1]", "u[m][2]");
        ss << string(dest);
        for (int k = 0; k < 3; k++)
        {
            sprintf(dest, "%d %18.10f %14.10f %14.10f %14.10f\n", k, t[k], u[k][0], u[k][1], u[k][2]);
            ss << string(dest);
        }
    }
    ss << "\nCode for rotating Structure 1 from (x,y,z) to (X,Y,Z):\n"
          "for(i=0; i<L; i++)\n"
          "{\n"
          "   X[i] = t[0] + u[0][0]*x[i] + u[0][1]*y[i] + u[0][2]*z[i];\n"
          "   Y[i] = t[1] + u[1][0]*x[i] + u[1][1]*y[i] + u[1][2]*z[i];\n"
          "   Z[i] = t[2] + u[2][0]*x[i] + u[2][1]*y[i] + u[2][2]*z[i];\n"
          "}\n";
    if (strcmp(fname_matrix, (char *)("-")) == 0)
        cout << ss.str();
    else
    {
        fstream fout;
        fout.open(fname_matrix, ios::out | ios::trunc);
        if (fout)
        {
            fout << ss.str();
            fout.close();
        }
        else
            cout << "Open file to output rotation matrix fail.\n";
    }
    ss.str(string());
}

void output_flexalign_chimerax(const string xname, const string yname,
                               const string fname_super, const vector<vector<double> > &tu_vec,
                               double t[3], double u[3][3], const int ter_opt,
                               const int mm_opt, const int split_opt, const int mirror_opt,
                               const char *seqM, const char *seqxA, const char *seqyA,
                               const vector<string> &resi_vec1, const vector<string> &resi_vec2,
                               const string chainID1, const string chainID2)
{
    int compress_type = 0; // uncompressed file
    ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
    ifstream fin_gz;
#else
    redi::ipstream fin_gz; // if file is compressed
    if (xname.size() >= 3 &&
        xname.substr(xname.size() - 3, 3) == ".gz")
    {
        fin_gz.open("gunzip -c " + xname);
        compress_type = 1;
    }
    else if (xname.size() >= 4 &&
             xname.substr(xname.size() - 4, 4) == ".bz2")
    {
        fin_gz.open("bzcat " + xname);
        compress_type = 2;
    }
    else
#endif
    fin.open(xname.c_str());

    map<string, int> resi2hinge_dict;
    int r, i, j;
    j = -1;
    char hinge_char = 0;
    int xlen = resi_vec1.size();
    int ali_len = strlen(seqM);
    for (r = 0; r < strlen(seqxA); r++)
    {
        if (seqxA[r] == '-')
            continue;
        j++;
        hinge_char = seqM[r];
        if (hinge_char == ' ')
        {
            for (i = 1; i < ali_len; i++)
            {
                if (r - i >= 0 && seqM[r - i] != ' ')
                    hinge_char = seqM[r - i];
                else if (r + i < xlen && seqM[r + i] != ' ')
                    hinge_char = seqM[r + i];
                if (hinge_char != ' ')
                    break;
            }
        }
        int hinge_idx = 0;
        if (hinge_char >= '0' && hinge_char <= '9')
        {
            hinge_idx = hinge_char - '0';
        }
        else if (hinge_char >= 'a' && hinge_char <= 'z')
        {
            hinge_idx = hinge_char - 'a' + 10;
        }
        else if (hinge_char >= 'A' && hinge_char <= 'Z')
        {
            hinge_idx = hinge_char - 'A' + 36;
        }
        resi2hinge_dict[resi_vec1[j]] = hinge_idx;
    }
    string resi = resi_vec1[0];
    int read_resi = resi.size() - 4;

    stringstream buf;
    stringstream buf_cxc;
    string line;
    double x[3];  // before transform
    double x1[3]; // after transform

    /* for PDBx/mmCIF only */
    map<string, int> _atom_site;
    size_t atom_site_pos;
    vector<string> line_vec;
    int infmt = -1; // 0 - PDB, 3 - PDBx/mmCIF
    int hinge = 0;
    string asym_id = "."; 
    
    while (compress_type ? fin_gz.good() : fin.good())
    {
        if (compress_type)
            getline(fin_gz, line);
        else
            getline(fin, line);
        if (line.compare(0, 6, "ATOM  ") == 0 ||
            line.compare(0, 6, "HETATM") == 0) // PDB format
        {
            infmt = 0;
            x[0] = atof(line.substr(30, 8).c_str());
            x[1] = atof(line.substr(38, 8).c_str());
            x[2] = atof(line.substr(46, 8).c_str());
            if (mirror_opt)
                x[2] = -x[2];
            if (read_resi == 1)
                resi = line.substr(22, 5);
            else
                resi = line.substr(22, 5) + line[21];
            hinge = 0;
            if (resi2hinge_dict.count(resi))
                hinge = resi2hinge_dict[resi];
            tu2t_u(tu_vec[hinge], t, u);
            transform(t, u, x, x1);
            buf << line.substr(0, 30) << setiosflags(ios::fixed)
                << setprecision(3)
                << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2]
                << line.substr(54) << '\n';
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            infmt = 3;
            buf << line << '\n';
            while (1)
            {
                if (compress_type)
                {
                    if (fin_gz.good())
                        getline(fin_gz, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                else
                {
                    if (fin.good())
                        getline(fin, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                if (line.size())
                    break;
            }
            buf << line << '\n';
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[Trim(line.substr(11))] = atom_site_pos;
            while (1)
            {
                while (1)
                {
                    if (compress_type)
                    {
                        if (fin_gz.good())
                            getline(fin_gz, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    else
                    {
                        if (fin.good())
                            getline(fin, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    if (line.size())
                        break;
                }
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[Trim(line.substr(11))] = ++atom_site_pos;
                buf << line << '\n';
            }

            if (_atom_site.count("group_PDB") *
                    _atom_site.count("Cartn_x") *
                    _atom_site.count("Cartn_y") *
                    _atom_site.count("Cartn_z") ==
                0)
            {
                buf << line << '\n';
                cerr << "Warning! Missing one of the following _atom_site data items: group_PDB, Cartn_x, Cartn_y, Cartn_z" << endl;
                continue;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;

                x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                if (mirror_opt)
                    x[2] = -x[2];

                if (_atom_site.count("auth_seq_id"))
                    resi = line_vec[_atom_site["auth_seq_id"]];
                else
                    resi = line_vec[_atom_site["label_seq_id"]];
                if (_atom_site.count("pdbx_PDB_ins_code") &&
                    line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                    resi += line_vec[_atom_site["pdbx_PDB_ins_code"]][0];
                else
                    resi += " ";
                if (read_resi >= 2)
                {
                    if (_atom_site.count("auth_asym_id"))
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    else
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    if (asym_id == ".")
                        asym_id = " ";
                    resi += asym_id[0];
                }
                hinge = 0;
                if (resi2hinge_dict.count(resi))
                    hinge = resi2hinge_dict[resi];
                tu2t_u(tu_vec[hinge], t, u);
                transform(t, u, x, x1);

                for (atom_site_pos = 0; atom_site_pos < _atom_site.size(); atom_site_pos++)
                {
                    if (atom_site_pos == _atom_site["Cartn_x"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[0] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_y"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[1] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_z"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[2] << ' ';
                    else
                        buf << line_vec[atom_site_pos] << ' ';
                }
                buf << '\n';

                if (compress_type && fin_gz.good())
                    getline(fin_gz, line);
                else if (!compress_type && fin.good())
                    getline(fin, line);
                else
                    break;
            }
            if (compress_type ? fin_gz.good() : fin.good())
                buf << line << '\n';
        }
        else if (line.size())
        {
            buf << line << '\n';
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    if (compress_type)
        fin_gz.close();
    else
        fin.close();

    string fname_super_full = fname_super;
    if (infmt == 0)
        fname_super_full += ".pdb";
    else if (infmt == 3)
        fname_super_full += ".cif";
    ofstream fp;
    fp.open(fname_super_full.c_str());
    fp << buf.str();
    fp.close();
    buf.str(string()); // clear stream

    string chain1_sele = "";
    string chain2_sele = "";
    if (!mm_opt)
    {
        if (split_opt == 2 && ter_opt >= 1) // align one chain from model 1
        {
            string c1 = chainID1.substr(1);
            string c2 = chainID2.substr(1);
            if (c1 != "_" && c1 != " " && c1 != "") chain1_sele = "/" + c1;
            if (c2 != "_" && c2 != " " && c2 != "") chain2_sele = "/" + c2;
        }
        else if (split_opt == 2 && ter_opt == 0) // align one chain from each model
        {
            string c1 = "", c2 = "";
            for (i = 1; i < chainID1.size(); i++)
                if (chainID1[i] == ',') break;
            c1 = chainID1.substr(i + 1);
            
            for (i = 1; i < chainID2.size(); i++)
                if (chainID2[i] == ',') break;
            c2 = chainID2.substr(i + 1);
            
            if (c1 != "_" && c1 != " " && c1 != "") chain1_sele = "/" + c1;
            if (c2 != "_" && c2 != " " && c2 != "") chain2_sele = "/" + c2;
        }
    }

    /* extract aligned region and group by hinge for ChimeraX visualization */
    int i1 = -1;
    int i2 = -1;
    int num_hinges = tu_vec.size(); // Total number of rigid bodies (hinges)
    
    // Arrays to store selections and bonds separately for each hinge
    vector<string> resi1_sele(num_hinges, "");
    vector<string> resi2_sele(num_hinges, "");
    vector<string> resi1_bond(num_hinges, "");
    vector<string> resi2_bond(num_hinges, "");
    vector<string> prev_resi1(num_hinges, "");
    vector<string> prev_resi2(num_hinges, "");
    string curr_resi1;
    string curr_resi2;

    if (!mm_opt)
    {
        for (i = 0; i < strlen(seqM); i++)
        {
            i1 += (seqxA[i] != '-' && seqxA[i] != '*');
            i2 += (seqyA[i] != '-');
            if (seqM[i] == ' ' || seqxA[i] == '*') continue;
            
            curr_resi1 = resi_vec1[i1].substr(0, 4);
            curr_resi2 = resi_vec2[i2].substr(0, 4);

            // Extract hinge index from the alignment mapping (seqM)
            char hinge_char = seqM[i];
            int hinge_idx = 0;
            if (hinge_char >= '0' && hinge_char <= '9') hinge_idx = hinge_char - '0';
            else if (hinge_char >= 'a' && hinge_char <= 'z') hinge_idx = hinge_char - 'a' + 10;
            else if (hinge_char >= 'A' && hinge_char <= 'Z') hinge_idx = hinge_char - 'A' + 36;
            
            // Safety check to prevent index out of bounds
            if (hinge_idx >= num_hinges) hinge_idx = num_hinges - 1;

            if (resi1_sele[hinge_idx].size() == 0)
                resi1_sele[hinge_idx] = curr_resi1;
            else
            {
                resi1_sele[hinge_idx] += "," + curr_resi1;
                // ChimeraX syntax to explicitly draw bonds between adjacent CA/C3'
                resi1_bond[hinge_idx] += "bond #1" + chain1_sele + ":" + prev_resi1[hinge_idx] + "@CA,C3' " +
                                         "#1" + chain1_sele + ":" + curr_resi1 + "@CA,C3'\n";
            }
            
            if (resi2_sele[hinge_idx].size() == 0)
                resi2_sele[hinge_idx] = curr_resi2;
            else
            {
                resi2_sele[hinge_idx] += "," + curr_resi2;
                resi2_bond[hinge_idx] += "bond #2" + chain2_sele + ":" + prev_resi2[hinge_idx] + "@CA,C3' " +
                                         "#2" + chain2_sele + ":" + curr_resi2 + "@CA,C3'\n";
            }
            
            prev_resi1[hinge_idx] = curr_resi1;
            prev_resi2[hinge_idx] = curr_resi2;
        }
        
        // Finalize ChimeraX selection syntax (e.g. ":1,2,3")
        for (int h = 0; h < num_hinges; h++)
        {
            if (resi1_sele[h].size()) resi1_sele[h] = ":" + resi1_sele[h];
            if (resi2_sele[h].size()) resi2_sele[h] = ":" + resi2_sele[h];
        }
    }

    /* write ChimeraX script (.cxc) */
    vector<string> cxc_list;
    cxc_list.push_back(fname_super + "");
    cxc_list.push_back(fname_super + "_atm");
    cxc_list.push_back(fname_super + "_all");
    cxc_list.push_back(fname_super + "_all_atm");
    cxc_list.push_back(fname_super + "_all_atm_lig");

    // Define 34 distinct HTML/CSS standard colors supported natively by ChimeraX 
    const char* color1_list[] = {
        "blue", "red", "green", "yellow", "magenta", "cyan", "orange", "purple", "brown", "pink",
        "navy", "firebrick", "forest green", "gold", "violet", "teal", "salmon", "aquamarine", "chocolate", "hot pink",
        "lime", "olive", "slate gray", "light blue", "light green", "light yellow", "light pink", "dodger blue", "medium blue",
        "dark violet", "crimson", "cornflower blue", "tomato", "sea green"
    };
    int color_palette_size = sizeof(color1_list) / sizeof(color1_list[0]);

    for (int p = 0; p < cxc_list.size(); p++)
    {
        if (mm_opt && p <= 1) continue;
        buf_cxc
            << "open " << fname_super_full << "\n"
            << "rename #1 structure1\n"
            << "open " << yname << "\n"
            << "rename #2 structure2\n"
            << "hide all\n"; // Hides all ribbons, atoms, etc.
            
        if (p == 0) // .cxc (Main Hinge Sticks)
        {
            // Remove unaligned chains/atoms
            if (chain1_sele.size())
                buf_cxc << "delete #1 & ~(" << chain1_sele << ")\n";
            if (chain2_sele.size())
                buf_cxc << "delete #2 & ~(" << chain2_sele << ")\n";
                
            buf_cxc << "delete ~@CA & ~@C3'\n";
            
            // Add bonds for each hinge
            for (int h = 0; h < num_hinges; h++) {
                buf_cxc << resi1_bond[h] << resi2_bond[h];
            }
            // Show sticks for each aligned hinge region
            for (int h = 0; h < num_hinges; h++) {
                if (resi1_sele[h].size()) {
                    buf_cxc << "show #1" << chain1_sele << resi1_sele[h] << " atoms\n";
                    buf_cxc << "style #1" << chain1_sele << resi1_sele[h] << " stick\n";
                }
                if (resi2_sele[h].size()) {
                    buf_cxc << "show #2" << chain2_sele << resi2_sele[h] << " atoms\n";
                    buf_cxc << "style #2" << chain2_sele << resi2_sele[h] << " stick\n";
                }
            }
        }
        else if (p == 1) // _atm.cxc (Hinge Cartoons)
        {
            for (int h = 0; h < num_hinges; h++) {
                if (resi1_sele[h].size()) buf_cxc << "cartoon #1" << chain1_sele << resi1_sele[h] << "\n";
                if (resi2_sele[h].size()) buf_cxc << "cartoon #2" << chain2_sele << resi2_sele[h] << "\n";
            }
        }
        else if (p == 2) // _all.cxc (Full Structure Cartoons)
        {
            buf_cxc
                << "cartoon #1" << chain1_sele << "\n"
                << "cartoon #2" << chain2_sele << "\n";
        }
        else if (p == 3) // _all_atm.cxc
        {
            buf_cxc
                << "cartoon #1" << chain1_sele << "\n"
                << "cartoon #2" << chain2_sele << "\n";
        }
        else if (p == 4) // _all_atm_lig.cxc (Cartoons + Ligands)
        {
            buf_cxc
                << "cartoon #1\n"
                << "cartoon #2\n"
                << "show ~polymer atoms\n"
                << "style ~polymer stick\n";
        }
        
        // Create selection groups in ChimeraX for easy user interaction (names)
        for (int h = 0; h < num_hinges; h++) {
            if (resi1_sele[h].size()) buf_cxc << "name hinge_" << h << "_str1 #1" << chain1_sele << resi1_sele[h] << "\n";
            if (resi2_sele[h].size()) buf_cxc << "name hinge_" << h << "_str2 #2" << chain2_sele << resi2_sele[h] << "\n";
        }
        
        // Global color setting: Structure 1 unaligned gets light gray, Structure 2 gets solid white
        buf_cxc
            << "color #1 gray\n"
            << "color #2 white\n";

        // Color each hinge in Structure 1 with distinct colors
        for (int h = 0; h < num_hinges; h++) {
            if (resi1_sele[h].size()) {
                buf_cxc << "color hinge_" << h << "_str1 " << color1_list[h % color_palette_size] << "\n";
            }
        }

        // Global display styling
        buf_cxc
            << "size stickradius 0.3\n"
            << "size atomradius 0.25\n"
            << "lighting shadows false\n"
            << "set bgColor white\n"
            << "transparency #1,2 20\n"
            << "view #1,2\n"
            << endl;

        fp.open((cxc_list[p] + ".cxc").c_str());
        fp << buf_cxc.str();
        fp.close();
        buf_cxc.str(string());
    }

    /* clean up */
    cxc_list.clear();
    resi1_sele.clear();
    resi2_sele.clear();
    resi1_bond.clear();
    resi2_bond.clear();
    prev_resi1.clear();
    prev_resi2.clear();
    chain1_sele.clear();
    chain2_sele.clear();
    resi2hinge_dict.clear();
}

void output_flexalign_rasmol(const string xname, const string yname,
                             const string fname_super, const vector<vector<double> > &tu_vec,
                             double t[3], double u[3][3], const int ter_opt,
                             const int mm_opt, const int split_opt, const int mirror_opt,
                             const char *seqM, const char *seqxA, const char *seqyA,
                             const vector<string> &resi_vec1, const vector<string> &resi_vec2,
                             const string chainID1, const string chainID2,
                             const int xlen, const int ylen, const double d0A, const int n_ali8,
                             const double rmsd, const double TM1, const double Liden)
{
    stringstream buf;
    stringstream buf_all;
    stringstream buf_atm;
    stringstream buf_all_atm;
    stringstream buf_all_atm_lig;
    // stringstream buf_pdb;
    stringstream buf_tm;
    string line;
    double x[3];    // before transform
    double x1[3];   // after transform
    bool after_ter; // true if passed the "TER" line in PDB
    string asym_id; // chain ID

    map<string, int> resi2hinge_dict;
    int r, i, j;
    j = -1;
    char hinge_char = 0;
    int ali_len = strlen(seqM);
    for (r = 0; r < strlen(seqxA); r++)
    {
        if (seqxA[r] == '-')
            continue;
        j++;
        hinge_char = seqM[r];
        if (hinge_char == ' ')
        {
            for (i = 1; i < ali_len; i++)
            {
                if (r - i >= 0 && seqM[r - i] != ' ')
                    hinge_char = seqM[r - i];
                else if (r + i < xlen && seqM[r + i] != ' ')
                    hinge_char = seqM[r + i];
                if (hinge_char != ' ')
                    break;
            }
        }
        int hinge_idx = 0;
        if (hinge_char >= '0' && hinge_char <= '9')
        {
            hinge_idx = hinge_char - '0';
        }
        else if (hinge_char >= 'a' && hinge_char <= 'z')
        {
            hinge_idx = hinge_char - 'a' + 10;
        }
        else if (hinge_char >= 'A' && hinge_char <= 'Z')
        {
            hinge_idx = hinge_char - 'A' + 36;
        }
        resi2hinge_dict[resi_vec1[j]] = hinge_idx;
    }
    string resi = resi_vec1[0];
    int read_resi = resi.size() - 4;

    buf_tm << "REMARK US-align"
           << "\nREMARK Structure 1:" << setw(11) << left << xname + chainID1 << " Size= " << xlen
           << "\nREMARK Structure 2:" << setw(11) << yname + chainID2 << right << " Size= " << ylen
           << " (TM-score is normalized by " << setw(4) << ylen << ", d0="
           << setiosflags(ios::fixed) << setprecision(2) << setw(6) << d0A << ")"
           << "\nREMARK Aligned length=" << setw(4) << n_ali8 << ", RMSD="
           << setw(6) << setiosflags(ios::fixed) << setprecision(2) << rmsd
           << ", TM-score=" << setw(7) << setiosflags(ios::fixed) << setprecision(5) << TM1
           << ", ID=" << setw(5) << setiosflags(ios::fixed) << setprecision(3)
           << ((n_ali8 > 0) ? Liden / n_ali8 : 0) << endl;
    string rasmol_CA_header = "load inline\nselect *A\nwireframe .45\nselect *B\nwireframe .20\nselect all\ncolor white\n";
    string rasmol_cartoon_header = "load inline\nselect all\ncartoon\nselect *A\ncolor blue\nselect *B\ncolor red\nselect ligand\nwireframe 0.25\nselect solvent\nspacefill 0.25\nselect all\nexit\n" + buf_tm.str();
    if (!mm_opt)
        buf << rasmol_CA_header;
    buf_all << rasmol_CA_header;
    if (!mm_opt)
        buf_atm << rasmol_cartoon_header;
    buf_all_atm << rasmol_cartoon_header;
    buf_all_atm_lig << rasmol_cartoon_header;

    /* selecting chains for -mol */
    string chain1_sele;
    string chain2_sele;
    if (!mm_opt)
    {
        if (split_opt == 2 && ter_opt >= 1) // align one chain from model 1
        {
            chain1_sele = chainID1.substr(1);
            chain2_sele = chainID2.substr(1);
        }
        else if (split_opt == 2 && ter_opt == 0) // align one chain from each model
        {
            for (i = 1; i < chainID1.size(); i++)
                if (chainID1[i] == ',')
                    break;
            chain1_sele = chainID1.substr(i + 1);
            for (i = 1; i < chainID2.size(); i++)
                if (chainID2[i] == ',')
                    break;
            chain2_sele = chainID2.substr(i + 1);
        }
    }

    /* for PDBx/mmCIF only */
    map<string, int> _atom_site;
    int atom_site_pos;
    vector<string> line_vec;
    string atom;        // 4-character atom name
    string AA;          // 3-character residue name
    string inscode;     // 1-character insertion code
    string model_index; // model index
    bool is_mmcif = false;

    /* used for CONECT record of chain1 */
    int ca_idx1 = 0;  // all CA atoms
    int lig_idx1 = 0; // all atoms
    vector<int> idx_vec;

    /* used for CONECT record of chain2 */
    int ca_idx2 = 0;  // all CA atoms
    int lig_idx2 = 0; // all atoms

    /* extract aligned region */
    vector<string> resi_aln1;
    vector<string> resi_aln2;
    int i1 = -1;
    int i2 = -1;
    if (!mm_opt)
    {
        for (i = 0; i < strlen(seqM); i++)
        {
            i1 += (seqxA[i] != '-');
            i2 += (seqyA[i] != '-');
            if (seqM[i] == ' ')
                continue;
            resi_aln1.push_back(resi_vec1[i1].substr(0, 4));
            resi_aln2.push_back(resi_vec2[i2].substr(0, 4));
            if (seqM[i] != ':')
                continue;
            buf << "select " << resi_aln1.back() << ":A,"
                << resi_aln2.back() << ":B\ncolor red\n";
            buf_all << "select " << resi_aln1.back() << ":A,"
                    << resi_aln2.back() << ":B\ncolor red\n";
        }
        buf << "select all\nexit\n"
            << buf_tm.str();
    }
    buf_all << "select all\nexit\n"
            << buf_tm.str();

    ifstream fin;
    /* read first file */
    after_ter = false;
    asym_id = "";
    fin.open(xname.c_str());
    int hinge = 0;
    while (fin.good())
    {
        getline(fin, line);
        if (ter_opt >= 3 && line.compare(0, 3, "TER") == 0)
            after_ter = true;
        if (is_mmcif == false && line.size() >= 54 &&
            (line.compare(0, 6, "ATOM  ") == 0 ||
             line.compare(0, 6, "HETATM") == 0)) // PDB format
        {
            if (line[16] != 'A' && line[16] != ' ')
                continue;
            x[0] = atof(line.substr(30, 8).c_str());
            x[1] = atof(line.substr(38, 8).c_str());
            x[2] = atof(line.substr(46, 8).c_str());
            if (mirror_opt)
                x[2] = -x[2];
            if (read_resi == 1)
                resi = line.substr(22, 5);
            else
                resi = line.substr(22, 5) + line[21];
            hinge = 0;
            if (resi2hinge_dict.count(resi))
                hinge = resi2hinge_dict[resi];
            tu2t_u(tu_vec[hinge], t, u);
            transform(t, u, x, x1);
            // buf_pdb<<line.substr(0,30)<<setiosflags(ios::fixed)
            //<<setprecision(3)
            //<<setw(8)<<x1[0] <<setw(8)<<x1[1] <<setw(8)<<x1[2]
            //<<line.substr(54)<<'\n';

            if (after_ter && line.compare(0, 6, "ATOM  ") == 0)
                continue;
            lig_idx1++;
            buf_all_atm_lig << line.substr(0, 6) << setw(5) << lig_idx1
                            << line.substr(11, 9) << " A" << line.substr(22, 8)
                            << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (chain1_sele.size() && line[21] != chain1_sele[0])
                continue;
            if (after_ter || line.compare(0, 6, "ATOM  "))
                continue;
            if (ter_opt >= 2)
            {
                if (ca_idx1 && asym_id.size() && asym_id != line.substr(21, 1))
                {
                    after_ter = true;
                    continue;
                }
                asym_id = line[21];
            }
            buf_all_atm << "ATOM  " << setw(5) << lig_idx1
                        << line.substr(11, 9) << " A" << line.substr(22, 8)
                        << setiosflags(ios::fixed) << setprecision(3)
                        << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (!mm_opt && find(resi_aln1.begin(), resi_aln1.end(),
                                line.substr(22, 4)) != resi_aln1.end())
            {
                buf_atm << "ATOM  " << setw(5) << lig_idx1
                        << line.substr(11, 9) << " A" << line.substr(22, 8)
                        << setiosflags(ios::fixed) << setprecision(3)
                        << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            }
            if (line.substr(12, 4) != " CA " && line.substr(12, 4) != " C3'")
                continue;
            ca_idx1++;
            buf_all << "ATOM  " << setw(5) << ca_idx1 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " A" << line.substr(22, 8)
                    << setiosflags(ios::fixed) << setprecision(3)
                    << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            if (find(resi_aln1.begin(), resi_aln1.end(),
                     line.substr(22, 4)) == resi_aln1.end())
                continue;
            if (!mm_opt)
                buf << "ATOM  " << setw(5) << ca_idx1 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " A" << line.substr(22, 8)
                    << setiosflags(ios::fixed) << setprecision(3)
                    << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2] << '\n';
            idx_vec.push_back(ca_idx1);
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                if (line.size())
                    break;
            }
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[line.substr(11, line.size() - 12)] = atom_site_pos;
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                if (line.size() == 0)
                    continue;
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[line.substr(11, line.size() - 12)] = ++atom_site_pos;
            }

            if (is_mmcif == false)
            {
                // buf_pdb.str(string());
                is_mmcif = true;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;
                if (_atom_site.count("pdbx_PDB_model_num"))
                {
                    if (model_index.size() && model_index !=
                                                  line_vec[_atom_site["pdbx_PDB_model_num"]])
                        break;
                    model_index = line_vec[_atom_site["pdbx_PDB_model_num"]];
                }

                x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                if (mirror_opt)
                    x[2] = -x[2];

                if (_atom_site.count("auth_seq_id"))
                    resi = line_vec[_atom_site["auth_seq_id"]];
                else
                    resi = line_vec[_atom_site["label_seq_id"]];
                if (_atom_site.count("pdbx_PDB_ins_code") &&
                    line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                    resi += line_vec[_atom_site["pdbx_PDB_ins_code"]][0];
                else
                    resi += " ";
                if (read_resi >= 2)
                {
                    if (_atom_site.count("auth_asym_id"))
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    else
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    if (asym_id == ".")
                        asym_id = " ";
                    resi += asym_id[0];
                }
                hinge = 0;
                if (resi2hinge_dict.count(resi))
                    hinge = resi2hinge_dict[resi];
                tu2t_u(tu_vec[hinge], t, u);
                transform(t, u, x, x1);

                if (_atom_site.count("label_alt_id") == 0 ||
                    line_vec[_atom_site["label_alt_id"]] == "." ||
                    line_vec[_atom_site["label_alt_id"]] == "A")
                {
                    atom = line_vec[_atom_site["label_atom_id"]];
                    if (atom[0] == '"')
                        atom = atom.substr(1);
                    if (atom.size() && atom[atom.size() - 1] == '"')
                        atom = atom.substr(0, atom.size() - 1);
                    if (atom.size() == 0)
                        atom = "    ";
                    else if (atom.size() == 1)
                        atom = " " + atom + "  ";
                    else if (atom.size() == 2)
                        atom = " " + atom + " ";
                    else if (atom.size() == 3)
                        atom = " " + atom;
                    else if (atom.size() >= 5)
                        atom = atom.substr(0, 4);

                    AA = line_vec[_atom_site["label_comp_id"]]; // residue name
                    if (AA.size() == 1)
                        AA = "  " + AA;
                    else if (AA.size() == 2)
                        AA = " " + AA;
                    else if (AA.size() >= 4)
                        AA = AA.substr(0, 3);

                    if (_atom_site.count("auth_seq_id"))
                        resi = line_vec[_atom_site["auth_seq_id"]];
                    else
                        resi = line_vec[_atom_site["label_seq_id"]];
                    while (resi.size() < 4)
                        resi = ' ' + resi;
                    if (resi.size() > 4)
                        resi = resi.substr(0, 4);

                    inscode = ' ';
                    if (_atom_site.count("pdbx_PDB_ins_code") &&
                        line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                        inscode = line_vec[_atom_site["pdbx_PDB_ins_code"]][0];

                    if (_atom_site.count("auth_asym_id"))
                    {
                        if (chain1_sele.size())
                            after_ter = line_vec[_atom_site["auth_asym_id"]] != chain1_sele;
                        else if (ter_opt >= 2 && ca_idx1 && asym_id.size() &&
                                 asym_id != line_vec[_atom_site["auth_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    }
                    else if (_atom_site.count("label_asym_id"))
                    {
                        if (chain1_sele.size())
                            after_ter = line_vec[_atom_site["label_asym_id"]] != chain1_sele;
                        if (ter_opt >= 2 && ca_idx1 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["label_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    }
                    // buf_pdb<<left<<setw(6)
                    //<<line_vec[_atom_site["group_PDB"]]<<right
                    //<<setw(5)<<lig_idx1%100000<<' '<<atom<<' '
                    //<<AA<<" "<<asym_id[asym_id.size()-1]
                    //<<resi<<inscode<<"   "
                    //<<setiosflags(ios::fixed)<<setprecision(3)
                    //<<setw(8)<<x1[0]
                    //<<setw(8)<<x1[1]
                    //<<setw(8)<<x1[2]<<'\n';

                    if (after_ter == false ||
                        line_vec[_atom_site["group_pdb"]] == "HETATM")
                    {
                        lig_idx1++;
                        buf_all_atm_lig << left << setw(6)
                                        << line_vec[_atom_site["group_PDB"]] << right
                                        << setw(5) << lig_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                        if (after_ter == false &&
                            line_vec[_atom_site["group_PDB"]] == "ATOM")
                        {
                            buf_all_atm << "ATOM  " << setw(6)
                                        << setw(5) << lig_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                            if (!mm_opt && find(resi_aln1.begin(),
                                                resi_aln1.end(), resi) != resi_aln1.end())
                            {
                                buf_atm << "ATOM  " << setw(6)
                                        << setw(5) << lig_idx1 % 100000 << ' '
                                        << atom << ' ' << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                            }
                            if (atom == " CA " || atom == " C3'")
                            {
                                ca_idx1++;
                                // mm_opt, split_opt, mirror_opt, chainID1,chainID2);
                                buf_all << "ATOM  " << setw(6)
                                        << setw(5) << ca_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                                if (!mm_opt && find(resi_aln1.begin(),
                                                    resi_aln1.end(), resi) != resi_aln1.end())
                                {
                                    buf << "ATOM  " << setw(6)
                                        << setw(5) << ca_idx1 % 100000 << ' ' << atom << ' '
                                        << AA << " A" << resi << inscode << "   "
                                        << setiosflags(ios::fixed) << setprecision(3)
                                        << setw(8) << x1[0]
                                        << setw(8) << x1[1]
                                        << setw(8) << x1[2] << '\n';
                                    idx_vec.push_back(ca_idx1);
                                }
                            }
                        }
                    }
                }

                while (1)
                {
                    if (fin.good())
                        getline(fin, line);
                    else
                        break;
                    if (line.size())
                        break;
                }
            }
        }
        else if (line.size() && is_mmcif == false)
        {
            // buf_pdb<<line<<'\n';
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    fin.close();
    if (!mm_opt)
        buf << "TER\n";
    buf_all << "TER\n";
    if (!mm_opt)
        buf_atm << "TER\n";
    buf_all_atm << "TER\n";
    buf_all_atm_lig << "TER\n";
    for (i = 1; i < ca_idx1; i++)
        buf_all << "CONECT"
                << setw(5) << i % 100000 << setw(5) << (i + 1) % 100000 << '\n';
    if (!mm_opt)
        for (i = 1; i < idx_vec.size(); i++)
            buf << "CONECT"
                << setw(5) << idx_vec[i - 1] % 100000 << setw(5) << idx_vec[i] % 100000 << '\n';
    idx_vec.clear();

    /* read second file */
    after_ter = false;
    asym_id = "";
    fin.open(yname.c_str());
    while (fin.good())
    {
        getline(fin, line);
        if (ter_opt >= 3 && line.compare(0, 3, "TER") == 0)
            after_ter = true;
        if (line.size() >= 54 && (line.compare(0, 6, "ATOM  ") == 0 ||
                                  line.compare(0, 6, "HETATM") == 0)) // PDB format
        {
            if (line[16] != 'A' && line[16] != ' ')
                continue;
            if (after_ter && line.compare(0, 6, "ATOM  ") == 0)
                continue;
            lig_idx2++;
            buf_all_atm_lig << line.substr(0, 6) << setw(5) << lig_idx1 + lig_idx2
                            << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            if (chain1_sele.size() && line[21] != chain1_sele[0])
                continue;
            if (after_ter || line.compare(0, 6, "ATOM  "))
                continue;
            if (ter_opt >= 2)
            {
                if (ca_idx2 && asym_id.size() && asym_id != line.substr(21, 1))
                {
                    after_ter = true;
                    continue;
                }
                asym_id = line[21];
            }
            buf_all_atm << "ATOM  " << setw(5) << lig_idx1 + lig_idx2
                        << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            if (!mm_opt && find(resi_aln2.begin(), resi_aln2.end(),
                                line.substr(22, 4)) != resi_aln2.end())
            {
                buf_atm << "ATOM  " << setw(5) << lig_idx1 + lig_idx2
                        << line.substr(11, 9) << " B" << line.substr(22, 32) << '\n';
            }
            if (line.substr(12, 4) != " CA " && line.substr(12, 4) != " C3'")
                continue;
            ca_idx2++;
            buf_all << "ATOM  " << setw(5) << ca_idx1 + ca_idx2 << ' ' << line.substr(12, 4)
                    << ' ' << line.substr(17, 3) << " B" << line.substr(22, 32) << '\n';
            if (find(resi_aln2.begin(), resi_aln2.end(), line.substr(22, 4)) == resi_aln2.end())
                continue;
            if (!mm_opt)
                buf << "ATOM  " << setw(5) << ca_idx1 + ca_idx2 << ' '
                    << line.substr(12, 4) << ' ' << line.substr(17, 3) << " B"
                    << line.substr(22, 32) << '\n';
            idx_vec.push_back(ca_idx1 + ca_idx2);
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + yname);
                if (line.size())
                    break;
            }
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[line.substr(11, line.size() - 12)] = atom_site_pos;
            while (1)
            {
                if (fin.good())
                    getline(fin, line);
                else
                    PrintErrorAndQuit("ERROR! Unexpected end of " + yname);
                if (line.size() == 0)
                    continue;
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[line.substr(11, line.size() - 12)] = ++atom_site_pos;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;
                if (_atom_site.count("pdbx_PDB_model_num"))
                {
                    if (model_index.size() && model_index !=
                                                  line_vec[_atom_site["pdbx_PDB_model_num"]])
                        break;
                    model_index = line_vec[_atom_site["pdbx_PDB_model_num"]];
                }

                if (_atom_site.count("label_alt_id") == 0 ||
                    line_vec[_atom_site["label_alt_id"]] == "." ||
                    line_vec[_atom_site["label_alt_id"]] == "A")
                {
                    atom = line_vec[_atom_site["label_atom_id"]];
                    if (atom[0] == '"')
                        atom = atom.substr(1);
                    if (atom.size() && atom[atom.size() - 1] == '"')
                        atom = atom.substr(0, atom.size() - 1);
                    if (atom.size() == 0)
                        atom = "    ";
                    else if (atom.size() == 1)
                        atom = " " + atom + "  ";
                    else if (atom.size() == 2)
                        atom = " " + atom + " ";
                    else if (atom.size() == 3)
                        atom = " " + atom;
                    else if (atom.size() >= 5)
                        atom = atom.substr(0, 4);

                    AA = line_vec[_atom_site["label_comp_id"]]; // residue name
                    if (AA.size() == 1)
                        AA = "  " + AA;
                    else if (AA.size() == 2)
                        AA = " " + AA;
                    else if (AA.size() >= 4)
                        AA = AA.substr(0, 3);

                    if (_atom_site.count("auth_seq_id"))
                        resi = line_vec[_atom_site["auth_seq_id"]];
                    else
                        resi = line_vec[_atom_site["label_seq_id"]];
                    while (resi.size() < 4)
                        resi = ' ' + resi;
                    if (resi.size() > 4)
                        resi = resi.substr(0, 4);

                    inscode = ' ';
                    if (_atom_site.count("pdbx_PDB_ins_code") &&
                        line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                        inscode = line_vec[_atom_site["pdbx_PDB_ins_code"]][0];

                    if (_atom_site.count("auth_asym_id"))
                    {
                        if (chain2_sele.size())
                            after_ter = line_vec[_atom_site["auth_asym_id"]] != chain2_sele;
                        if (ter_opt >= 2 && ca_idx2 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["auth_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    }
                    else if (_atom_site.count("label_asym_id"))
                    {
                        if (chain2_sele.size())
                            after_ter = line_vec[_atom_site["label_asym_id"]] != chain2_sele;
                        if (ter_opt >= 2 && ca_idx2 && asym_id.size() &&
                            asym_id != line_vec[_atom_site["label_asym_id"]])
                            after_ter = true;
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    }
                    if (after_ter == false ||
                        line_vec[_atom_site["group_PDB"]] == "HETATM")
                    {
                        lig_idx2++;
                        buf_all_atm_lig << left << setw(6)
                                        << line_vec[_atom_site["group_PDB"]] << right
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                        if (after_ter == false &&
                            line_vec[_atom_site["group_PDB"]] == "ATOM")
                        {
                            buf_all_atm << "ATOM  " << setw(6)
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                            if (!mm_opt && find(resi_aln2.begin(),
                                                resi_aln2.end(), resi) != resi_aln2.end())
                            {
                                buf_atm << "ATOM  " << setw(6)
                                        << setw(5) << (lig_idx1 + lig_idx2) % 100000 << ' '
                                        << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                            }
                            if (atom == " CA " || atom == " C3'")
                            {
                                ca_idx2++;
                                buf_all << "ATOM  " << setw(6)
                                        << setw(5) << (ca_idx1 + ca_idx2) % 100000
                                        << ' ' << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                                if (!mm_opt && find(resi_aln2.begin(),
                                                    resi_aln2.end(), resi) != resi_aln2.end())
                                {
                                    buf << "ATOM  " << setw(6)
                                        << setw(5) << (ca_idx1 + ca_idx2) % 100000
                                        << ' ' << atom << ' ' << AA << " B" << resi << inscode << "   "
                                        << setw(8) << line_vec[_atom_site["Cartn_x"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_y"]]
                                        << setw(8) << line_vec[_atom_site["Cartn_z"]]
                                        << '\n';
                                    idx_vec.push_back(ca_idx1 + ca_idx2);
                                }
                            }
                        }
                    }
                }

                if (fin.good())
                    getline(fin, line);
                else
                    break;
            }
        }
        else if (line.size())
        {
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    fin.close();
    if (!mm_opt)
        buf << "TER\n";
    buf_all << "TER\n";
    if (!mm_opt)
        buf_atm << "TER\n";
    buf_all_atm << "TER\n";
    buf_all_atm_lig << "TER\n";
    for (i = ca_idx1 + 1; i < ca_idx1 + ca_idx2; i++)
        buf_all << "CONECT"
                << setw(5) << i % 100000 << setw(5) << (i + 1) % 100000 << '\n';
    for (i = 1; i < idx_vec.size(); i++)
        buf << "CONECT"
            << setw(5) << idx_vec[i - 1] % 100000 << setw(5) << idx_vec[i] % 100000 << '\n';
    idx_vec.clear();

    /* write pymol script */
    ofstream fp;
    /*
    stringstream buf_pymol;
    vector<string> pml_list;
    pml_list.push_back(fname_super+"");
    pml_list.push_back(fname_super+"_atm");
    pml_list.push_back(fname_super+"_all");
    pml_list.push_back(fname_super+"_all_atm");
    pml_list.push_back(fname_super+"_all_atm_lig");
    for (i=0;i<pml_list.size();i++)
    {
        buf_pymol<<"#!/usr/bin/env pymol\n"
            <<"load "<<pml_list[i]<<"\n"
            <<"hide all\n"
            <<((i==0 || i==2)?("show stick\n"):("show cartoon\n"))
            <<"color blue, chain A\n"
            <<"color red, chain B\n"
            <<"set ray_shadow, 0\n"
            <<"set stick_radius, 0.3\n"
            <<"set sphere_scale, 0.25\n"
            <<"show stick, not polymer\n"
            <<"show sphere, not polymer\n"
            <<"bg_color white\n"
            <<"set transparency=0.2\n"
            <<"zoom polymer\n"
            <<endl;
        fp.open((pml_list[i]+".pml").c_str());
        fp<<buf_pymol.str();
        fp.close();
        buf_pymol.str(string());
        pml_list[i].clear();
    }
    pml_list.clear();
    */

    /* write rasmol script */
    if (!mm_opt)
    {
        fp.open((fname_super+".spt").c_str());
        fp << buf.str();
        fp.close();
    }
    fp.open((fname_super + "_all.spt").c_str());
    fp << buf_all.str();
    fp.close();
    if (!mm_opt)
    {
        fp.open((fname_super + "_atm.spt").c_str());
        fp << buf_atm.str();
        fp.close();
    }
    fp.open((fname_super + "_all_atm.spt").c_str());
    fp << buf_all_atm.str();
    fp.close();
    fp.open((fname_super + "_all_atm_lig.spt").c_str());
    fp << buf_all_atm_lig.str();
    fp.close();
    // fp.open((fname_super+".pdb").c_str());
    // fp<<buf_pdb.str();
    // fp.close();

    /* clear stream */
    buf.str(string());
    buf_all.str(string());
    buf_atm.str(string());
    buf_all_atm.str(string());
    buf_all_atm_lig.str(string());
    // buf_pdb.str(string());
    buf_tm.str(string());
    resi_aln1.clear();
    resi_aln2.clear();
    asym_id.clear();
    line_vec.clear();
    atom.clear();
    AA.clear();
    resi.clear();
    inscode.clear();
    model_index.clear();
}

void output_flexalign_pymol(const string xname, const string yname,
                            const string fname_super, const vector<vector<double> > &tu_vec,
                            double t[3], double u[3][3], const int ter_opt,
                            const int mm_opt, const int split_opt, const int mirror_opt,
                            const char *seqM, const char *seqxA, const char *seqyA,
                            const vector<string> &resi_vec1, const vector<string> &resi_vec2,
                            const string chainID1, const string chainID2)
{
    int compress_type = 0; // uncompressed file
    ifstream fin;
#ifndef REDI_PSTREAM_H_SEEN
    ifstream fin_gz;
#else
    redi::ipstream fin_gz; // if file is compressed
    if (xname.size() >= 3 &&
        xname.substr(xname.size() - 3, 3) == ".gz")
    {
        fin_gz.open("gunzip -c " + xname);
        compress_type = 1;
    }
    else if (xname.size() >= 4 &&
             xname.substr(xname.size() - 4, 4) == ".bz2")
    {
        fin_gz.open("bzcat " + xname);
        compress_type = 2;
    }
    else
#endif
    fin.open(xname.c_str());

    map<string, int> resi2hinge_dict;
    int r, i, j;
    j = -1;
    char hinge_char = 0;
    int xlen = resi_vec1.size();
    int ali_len = strlen(seqM);
    for (r = 0; r < strlen(seqxA); r++)
    {
        if (seqxA[r] == '-')
            continue;
        j++;
        hinge_char = seqM[r];
        if (hinge_char == ' ')
        {
            for (i = 1; i < ali_len; i++)
            {
                if (r - i >= 0 && seqM[r - i] != ' ')
                    hinge_char = seqM[r - i];
                else if (r + i < xlen && seqM[r + i] != ' ')
                    hinge_char = seqM[r + i];
                if (hinge_char != ' ')
                    break;
            }
        }
        int hinge_idx = 0;
        if (hinge_char >= '0' && hinge_char <= '9')
        {
            hinge_idx = hinge_char - '0';
        }
        else if (hinge_char >= 'a' && hinge_char <= 'z')
        {
            hinge_idx = hinge_char - 'a' + 10;
        }
        else if (hinge_char >= 'A' && hinge_char <= 'Z')
        {
            hinge_idx = hinge_char - 'A' + 36;
        }
        resi2hinge_dict[resi_vec1[j]] = hinge_idx;
    }
    string resi = resi_vec1[0];
    int read_resi = resi.size() - 4;

    stringstream buf;
    stringstream buf_pymol;
    string line;
    double x[3];  // before transform
    double x1[3]; // after transform

    /* for PDBx/mmCIF only */
    map<string, int> _atom_site;
    size_t atom_site_pos;
    vector<string> line_vec;
    int infmt = -1; // 0 - PDB, 3 - PDBx/mmCIF
    int hinge = 0;
    string asym_id = "."; // this is similar to chainID, except that
                          // chainID is char while asym_id is a string
                          // with possibly multiple char
    while (compress_type ? fin_gz.good() : fin.good())
    {
        if (compress_type)
            getline(fin_gz, line);
        else
            getline(fin, line);
        if (line.compare(0, 6, "ATOM  ") == 0 ||
            line.compare(0, 6, "HETATM") == 0) // PDB format
        {
            infmt = 0;
            x[0] = atof(line.substr(30, 8).c_str());
            x[1] = atof(line.substr(38, 8).c_str());
            x[2] = atof(line.substr(46, 8).c_str());
            if (mirror_opt)
                x[2] = -x[2];
            if (read_resi == 1)
                resi = line.substr(22, 5);
            else
                resi = line.substr(22, 5) + line[21];
            hinge = 0;
            if (resi2hinge_dict.count(resi))
                hinge = resi2hinge_dict[resi];
            tu2t_u(tu_vec[hinge], t, u);
            transform(t, u, x, x1);
            buf << line.substr(0, 30) << setiosflags(ios::fixed)
                << setprecision(3)
                << setw(8) << x1[0] << setw(8) << x1[1] << setw(8) << x1[2]
                << line.substr(54) << '\n';
        }
        else if (line.compare(0, 5, "loop_") == 0) // PDBx/mmCIF
        {
            infmt = 3;
            buf << line << '\n';
            while (1)
            {
                if (compress_type)
                {
                    if (fin_gz.good())
                        getline(fin_gz, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                else
                {
                    if (fin.good())
                        getline(fin, line);
                    else
                        PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                }
                if (line.size())
                    break;
            }
            buf << line << '\n';
            if (line.compare(0, 11, "_atom_site."))
                continue;
            _atom_site.clear();
            atom_site_pos = 0;
            _atom_site[Trim(line.substr(11))] = atom_site_pos;
            while (1)
            {
                while (1)
                {
                    if (compress_type)
                    {
                        if (fin_gz.good())
                            getline(fin_gz, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    else
                    {
                        if (fin.good())
                            getline(fin, line);
                        else
                            PrintErrorAndQuit("ERROR! Unexpected end of " + xname);
                    }
                    if (line.size())
                        break;
                }
                if (line.compare(0, 11, "_atom_site."))
                    break;
                _atom_site[Trim(line.substr(11))] = ++atom_site_pos;
                buf << line << '\n';
            }

            if (_atom_site.count("group_PDB") *
                    _atom_site.count("Cartn_x") *
                    _atom_site.count("Cartn_y") *
                    _atom_site.count("Cartn_z") ==
                0)
            {
                buf << line << '\n';
                cerr << "Warning! Missing one of the following _atom_site data items: group_PDB, Cartn_x, Cartn_y, Cartn_z" << endl;
                continue;
            }

            while (1)
            {
                line_vec.clear();
                split(line, line_vec);
                if (line_vec[_atom_site["group_PDB"]] != "ATOM" &&
                    line_vec[_atom_site["group_PDB"]] != "HETATM")
                    break;

                x[0] = atof(line_vec[_atom_site["Cartn_x"]].c_str());
                x[1] = atof(line_vec[_atom_site["Cartn_y"]].c_str());
                x[2] = atof(line_vec[_atom_site["Cartn_z"]].c_str());
                if (mirror_opt)
                    x[2] = -x[2];

                if (_atom_site.count("auth_seq_id"))
                    resi = line_vec[_atom_site["auth_seq_id"]];
                else
                    resi = line_vec[_atom_site["label_seq_id"]];
                if (_atom_site.count("pdbx_PDB_ins_code") &&
                    line_vec[_atom_site["pdbx_PDB_ins_code"]] != "?")
                    resi += line_vec[_atom_site["pdbx_PDB_ins_code"]][0];
                else
                    resi += " ";
                if (read_resi >= 2)
                {
                    if (_atom_site.count("auth_asym_id"))
                        asym_id = line_vec[_atom_site["auth_asym_id"]];
                    else
                        asym_id = line_vec[_atom_site["label_asym_id"]];
                    if (asym_id == ".")
                        asym_id = " ";
                    resi += asym_id[0];
                }
                hinge = 0;
                if (resi2hinge_dict.count(resi))
                    hinge = resi2hinge_dict[resi];
                tu2t_u(tu_vec[hinge], t, u);
                transform(t, u, x, x1);

                for (atom_site_pos = 0; atom_site_pos < _atom_site.size(); atom_site_pos++)
                {
                    if (atom_site_pos == _atom_site["Cartn_x"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[0] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_y"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[1] << ' ';
                    else if (atom_site_pos == _atom_site["Cartn_z"])
                        buf << setiosflags(ios::fixed) << setprecision(3)
                            << setw(8) << x1[2] << ' ';
                    else
                        buf << line_vec[atom_site_pos] << ' ';
                }
                buf << '\n';

                if (compress_type && fin_gz.good())
                    getline(fin_gz, line);
                else if (!compress_type && fin.good())
                    getline(fin, line);
                else
                    break;
            }
            if (compress_type ? fin_gz.good() : fin.good())
                buf << line << '\n';
        }
        else if (line.size())
        {
            buf << line << '\n';
            if (ter_opt >= 1 && line.compare(0, 3, "END") == 0)
                break;
        }
    }
    if (compress_type)
        fin_gz.close();
    else
        fin.close();

    string fname_super_full = fname_super;
    if (infmt == 0)
        fname_super_full += ".pdb";
    else if (infmt == 3)
        fname_super_full += ".cif";
    ofstream fp;
    fp.open(fname_super_full.c_str());
    fp << buf.str();
    fp.close();
    buf.str(string()); // clear stream

    string chain1_sele;
    string chain2_sele;
    if (!mm_opt)
    {
        if (split_opt == 2 && ter_opt >= 1) // align one chain from model 1
        {
            if (chainID1.substr(1)!="_")
                chain1_sele = " and c. " + chainID1.substr(1);
            if (chainID2.substr(1)!="_")
                chain2_sele = " and c. " + chainID2.substr(1);
        }
        else if (split_opt == 2 && ter_opt == 0) // align one chain from each model
        {
            for (i = 1; i < chainID1.size(); i++)
                if (chainID1[i] == ',')
                    break;
            if (chainID1.substr(i+1)!="_")
                chain1_sele = " and c. " + chainID1.substr(i + 1);
            for (i = 1; i < chainID2.size(); i++)
                if (chainID2[i] == ',')
                    break;
            if (chainID2.substr(i+1)!="_")
                chain2_sele = " and c. " + chainID2.substr(i + 1);
        }
    }

    /* extract aligned region and group by hinge for PyMOL visualization */
    int i1 = -1;
    int i2 = -1;
    int num_hinges = tu_vec.size(); // Total number of rigid bodies (hinges)

    // Arrays to store selections and bonds separately for each hinge
    vector<string> resi1_sele(num_hinges, "");
    vector<string> resi2_sele(num_hinges, "");
    vector<string> resi1_bond(num_hinges, "");
    vector<string> resi2_bond(num_hinges, "");
    vector<string> prev_resi1(num_hinges, "");
    vector<string> prev_resi2(num_hinges, "");
    string curr_resi1;
    string curr_resi2;

    if (mm_opt)
    {
        ;
    }
    else
    {
        for (i = 0; i < strlen(seqM); i++)
        {
            i1 += (seqxA[i] != '-' && seqxA[i] != '*');
            i2 += (seqyA[i] != '-');
            if (seqM[i] == ' ' || seqxA[i] == '*') continue;

            curr_resi1 = resi_vec1[i1].substr(0, 4);
            curr_resi2 = resi_vec2[i2].substr(0, 4);

            // Extract hinge index from the alignment mapping (seqM)
            char hinge_char = seqM[i];
            int hinge_idx = 0;
            if (hinge_char >= '0' && hinge_char <= '9') hinge_idx = hinge_char - '0';
            else if (hinge_char >= 'a' && hinge_char <= 'z') hinge_idx = hinge_char - 'a' + 10;
            else if (hinge_char >= 'A' && hinge_char <= 'Z') hinge_idx = hinge_char - 'A' + 36;

            // Safety check to prevent index out of bounds
            if (hinge_idx >= num_hinges) hinge_idx = num_hinges - 1;

            if (resi1_sele[hinge_idx].size() == 0)
                resi1_sele[hinge_idx] = "i. " + curr_resi1;
            else
            {
                resi1_sele[hinge_idx] += " or i. " + curr_resi1;
                resi1_bond[hinge_idx] += "bond structure1 and i. " + prev_resi1[hinge_idx] +
                              ", i. " + curr_resi1 + "\n";
            }

            if (resi2_sele[hinge_idx].size() == 0)
                resi2_sele[hinge_idx] = "i. " + curr_resi2;
            else
            {
                resi2_sele[hinge_idx] += " or i. " + curr_resi2;
                resi2_bond[hinge_idx] += "bond structure2 and i. " + prev_resi2[hinge_idx] +
                              ", i. " + curr_resi2 + "\n";
            }

            prev_resi1[hinge_idx] = curr_resi1;
            prev_resi2[hinge_idx] = curr_resi2;
        }

        // Wrap final selections in parentheses
        for (int h = 0; h < num_hinges; h++)
        {
            if (resi1_sele[h].size()) resi1_sele[h] = " and ( " + resi1_sele[h] + ")";
            if (resi2_sele[h].size()) resi2_sele[h] = " and ( " + resi2_sele[h] + ")";
        }
    }

    /* write pymol script */
    vector<string> pml_list;
    pml_list.push_back(fname_super + "");
    pml_list.push_back(fname_super + "_atm");
    pml_list.push_back(fname_super + "_all");
    pml_list.push_back(fname_super + "_all_atm");
    pml_list.push_back(fname_super + "_all_atm_lig");

    // Define 35 distinct PyMOL colors for Structure 1 
    // The first 10 are highly contrasting for standard cases
    const char* color1_list[] = {
        "blue", "red", "green", "yellow", "magenta", "cyan", "orange", "purple", "brown", "pink", // Top 10 highly contrasting
        "marine", "firebrick", "forest", "gold", "violet", "deepteal", "salmon", "aquamarine", "chocolate", "hotpink",
        "lime", "olive", "slate", "paleblue", "palegreen", "paleyellow", "lightpink", "lightblue", "lightmagenta",
        "dirtyviolet", "dash", "tv_blue", "tv_red", "tv_green" // Extended palette to safely cover >31 hinges
    };
    int color_palette_size = sizeof(color1_list) / sizeof(color1_list[0]);

    for (int p = 0; p < pml_list.size(); p++)
    {
        if (mm_opt && p <= 1) continue;
        buf_pymol
            << "#!/usr/bin/env pymol\n"
            << "cmd.load(\"" << fname_super_full << "\", \"structure1\")\n"
            << "cmd.load(\"" << yname << "\", \"structure2\")\n"
            << "hide all\n"
            << "set all_states, " << ((ter_opt == 0) ? "on" : "off") << '\n';

        if (p == 0) // .pml
        {
            if (chain1_sele.size())
                buf_pymol << "remove structure1 and not " << chain1_sele.substr(4) << "\n";
            if (chain2_sele.size())
                buf_pymol << "remove structure2 and not " << chain2_sele.substr(4) << "\n";
            buf_pymol << "remove not n. CA and not n. C3'\n";

            // Add bonds for each hinge
            for (int h = 0; h < num_hinges; h++) {
                buf_pymol << resi1_bond[h] << resi2_bond[h];
            }
            // Show sticks for each aligned hinge region
            for (int h = 0; h < num_hinges; h++) {
                if (resi1_sele[h].size()) buf_pymol << "show stick, structure1" << chain1_sele << resi1_sele[h] << "\n";
                if (resi2_sele[h].size()) buf_pymol << "show stick, structure2" << chain2_sele << resi2_sele[h] << "\n";
            }
        }
        else if (p == 1) // _atm.pml
        {
            // Show cartoons for aligned hinge regions
            for (int h = 0; h < num_hinges; h++) {
                if (resi1_sele[h].size()) buf_pymol << "show cartoon, structure1" << chain1_sele << resi1_sele[h] << "\n";
                if (resi2_sele[h].size()) buf_pymol << "show cartoon, structure2" << chain2_sele << resi2_sele[h] << "\n";
            }
        }
        else if (p == 2) // _all.pml
        {
            buf_pymol
                << "show ribbon, structure1" << chain1_sele << "\n"
                << "show ribbon, structure2" << chain2_sele << "\n";
        }
        else if (p == 3) // _all_atm.pml
        {
            buf_pymol
                << "show cartoon, structure1" << chain1_sele << "\n"
                << "show cartoon, structure2" << chain2_sele << "\n";
        }
        else if (p == 4) // _all_atm_lig.pml
        {
            buf_pymol
                << "show cartoon, structure1\n"
                << "show cartoon, structure2\n"
                << "show stick, not polymer\n"
                << "show sphere, not polymer\n";
        }
        
        // Create selection groups in PyMOL for easy user interaction
        for (int h = 0; h < num_hinges; h++) {
            if (resi1_sele[h].size()) buf_pymol << "select hinge_" << h << "_str1, structure1" << chain1_sele << resi1_sele[h] << "\n";
            if (resi2_sele[h].size()) buf_pymol << "select hinge_" << h << "_str2, structure2" << chain2_sele << resi2_sele[h] << "\n";
        }
        
        // Global color setting: Structure 1 unaligned gets light gray, Structure 2 gets solid darker gray
        buf_pymol
            << "color grey, structure1\n"
            << "color white, structure2\n";

        // Color each hinge in Structure 1 with distinct colors
        for (int h = 0; h < num_hinges; h++) {
            if (resi1_sele[h].size()) {
                buf_pymol << "color " << color1_list[h % color_palette_size] << ", hinge_" << h << "_str1\n";
            }
            // Structure 2 remains gray, so we don't apply specific colors to it
        }

        buf_pymol
            << "set ribbon_width, 6\n"
            << "set stick_radius, 0.3\n"
            << "set sphere_scale, 0.25\n"
            << "set ray_shadow, 0\n"
            << "bg_color white\n"
            << "set transparency=0.2\n"
            << "zoom polymer and ((structure1" << chain1_sele
            << ") or (structure2" << chain2_sele << "))\n"
            << endl;

        fp.open((pml_list[p] + ".pml").c_str());
        fp << buf_pymol.str();
        fp.close();
        buf_pymol.str(string());
    }

    /* clean up */
    pml_list.clear();

    resi1_sele.clear();
    resi2_sele.clear();

    resi1_bond.clear();
    resi2_bond.clear();

    prev_resi1.clear();
    prev_resi2.clear();

    curr_resi1.clear();
    curr_resi2.clear();

    chain1_sele.clear();
    chain2_sele.clear();
    resi2hinge_dict.clear();
}

// output the final results
void output_flexalign_results(const string xname, const string yname,
                              const string chainID1, const string chainID2,
                              const int xlen, const int ylen, double t[3], double u[3][3],
                              const vector<vector<double> > &tu_vec, const double TM1, const double TM2,
                              const double TM3, const double TM4, const double TM5,
                              const double rmsd, const double d0_out, const char *seqM,
                              const char *seqxA, const char *seqyA, const double Liden,
                              const int n_ali8, const int L_ali, const double TM_ali,
                              const double rmsd_ali, const double TM_0, const double d0_0,
                              const double d0A, const double d0B, const double Lnorm_ass,
                              const double d0_scale, const double d0a, const double d0u,
                              const char *fname_matrix, const int outfmt_opt, const int ter_opt,
                              const int mm_opt, const int split_opt, const int o_opt,
                              const string fname_super, const int i_opt, const int a_opt,
                              const bool u_opt, const bool d_opt, const int mirror_opt,
                              const vector<string> &resi_vec1, const vector<string> &resi_vec2)
{
    if (outfmt_opt <= 0)
    {
        printf("\nName of Structure_1: %s%s (to be superimposed onto Structure_2)\n",
               xname.c_str(), chainID1.c_str());
        printf("Name of Structure_2: %s%s\n", yname.c_str(), chainID2.c_str());
        printf("Length of Structure_1: %d residues\n", xlen);
        printf("Length of Structure_2: %d residues\n\n", ylen);

        if (i_opt)
            printf("User-specified initial alignment: TM/Lali/rmsd = %7.5lf, %4d, %6.3lf\n", TM_ali, L_ali, rmsd_ali);

        printf("Aligned length= %d, RMSD= %6.2f, Seq_ID=n_identical/n_aligned= %4.3f\n", n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);
        printf("TM-score= %6.5f (normalized by length of Structure_1: L=%d, d0=%.2f)\n", TM2, xlen, d0B);
        printf("TM-score= %6.5f (normalized by length of Structure_2: L=%d, d0=%.2f)\n", TM1, ylen, d0A);

        if (a_opt == 1)
            printf("TM-score= %6.5f (if normalized by average length of two structures: L=%.1f, d0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);
        if (u_opt)
            printf("TM-score= %6.5f (normalized by user-specified L=%.2f and d0=%.2f)\n", TM4, Lnorm_ass, d0u);
        if (d_opt)
            printf("TM-score= %6.5f (scaled by user-specified d0=%.2f, and L=%d)\n", TM5, d0_scale, ylen);
        printf("(You should use TM-score normalized by length of the reference structure)\n");

        // output alignment
        printf("\n([0-9,a-z,A-Z] denote different aligned fragment pairs separated by different hinges)\n");
        printf("%s\n", seqxA);
        printf("%s\n", seqM);
        printf("%s\n", seqyA);
    }
    else if (outfmt_opt == 1)
    {
        printf(">%s%s\tL=%d\td0=%.2f\tseqID=%.3f\tTM-score=%.5f\n",
               xname.c_str(), chainID1.c_str(), xlen, d0B, Liden / xlen, TM2);
        printf("%s\n", seqxA);
        printf(">%s%s\tL=%d\td0=%.2f\tseqID=%.3f\tTM-score=%.5f\n",
               yname.c_str(), chainID2.c_str(), ylen, d0A, Liden / ylen, TM1);
        printf("%s\n", seqyA);

        printf("# Lali=%d\tRMSD=%.2f\tseqID_ali=%.3f\n",
               n_ali8, rmsd, (n_ali8 > 0) ? Liden / n_ali8 : 0);

        if (i_opt)
            printf("# User-specified initial alignment: TM=%.5lf\tLali=%4d\trmsd=%.3lf\n", TM_ali, L_ali, rmsd_ali);

        if (a_opt)
            printf("# TM-score=%.5f (normalized by average length of two structures: L=%.1f\td0=%.2f)\n", TM3, (xlen + ylen) * 0.5, d0a);

        if (u_opt)
            printf("# TM-score=%.5f (normalized by user-specified L=%.2f\td0=%.2f)\n", TM4, Lnorm_ass, d0u);

        if (d_opt)
            printf("# TM-score=%.5f (scaled by user-specified d0=%.2f\tL=%d)\n", TM5, d0_scale, ylen);

        printf("$$$$\n");
    }
    else if (outfmt_opt == 2)
    {
        printf("%s%s\t%s%s\t%.4f\t%.4f\t%.2f\t%4.3f\t%4.3f\t%4.3f\t%d\t%d\t%d\t%d",
               xname.c_str(), chainID1.c_str(), yname.c_str(), chainID2.c_str(),
               TM2, TM1, rmsd, Liden / xlen, Liden / ylen, (n_ali8 > 0) ? Liden / n_ali8 : 0,
               xlen, ylen, n_ali8, (int)tu_vec.size());
    }
    cout << endl;

    if (strlen(fname_matrix))
        output_flexalign_rotation_matrix(
            fname_matrix, tu_vec, t, u);

    if (o_opt == 1)
        output_flexalign_pymol(xname, yname, fname_super, tu_vec,
                               t, u, ter_opt, mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                               resi_vec1, resi_vec2, chainID1, chainID2);
    else if (o_opt == 2)
        output_flexalign_rasmol(xname, yname, fname_super, tu_vec,
                                t, u, ter_opt, mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                                resi_vec1, resi_vec2, chainID1, chainID2,
                                xlen, ylen, d0A, n_ali8, rmsd, TM1, Liden);
    else if (o_opt == 3)
        output_flexalign_chimerax(xname, yname, fname_super, tu_vec,
                                  t, u, ter_opt, mm_opt, split_opt, mirror_opt, seqM, seqxA, seqyA,
                                  resi_vec1, resi_vec2, chainID1, chainID2);
}

// Data structure to hold outputs of flexalign_main to avoid parameter clutter
struct FlexAlignResult
{
    double t0[3];
    double u0[3][3];
    vector<vector<double> > tu_vec;
    double TM1, TM2, TM3, TM4, TM5;
    double d0_0, TM_0, d0A, d0B, d0u, d0a, d0_out;
    string seqM, seqxA, seqyA;
    vector<double> do_vec;
    double rmsd0, Liden, TM_ali, rmsd_ali;
    int L_ali, n_ali, n_ali8, hingeNum;

    FlexAlignResult() : TM1(-1.0), TM2(-1.0), TM3(-1.0), TM4(-1.0), TM5(-1.0),
                        d0_0(0.0), TM_0(0.0), d0A(0.0), d0B(0.0), d0u(0.0), d0a(0.0), d0_out(5.0),
                        rmsd0(0.0), Liden(0.0), TM_ali(0.0), rmsd_ali(0.0),
                        L_ali(0), n_ali(0), n_ali8(0), hingeNum(0)
    {
        for (int i = 0; i < 3; i++)
        {
            t0[i] = 0.0;
            for (int j = 0; j < 3; j++)
                u0[i][j] = (i == j) ? 1.0 : 0.0;
        }
    }
};

enum FlexAlignMode
{
    FLEX_STANDARD = 0,
    FLEX_BEST = 1,
    FLEX_USBCAT = 2
};

// Encapsulates the execution of flexalign_main and its fallback refinement logic
void execute_flexalign_with_fallback(
    double **xa, double **ya, char *seqx, char *seqy, char *secx, char *secy,
    int xlen, int ylen, vector<string> &sequence, const double Lnorm_ass, const double d0_scale,
    const int i_opt, const int a_opt, const bool u_opt, const bool d_opt, const bool force_fast_opt,
    const int mol_type, const int hinge_opt, const int ss_opt, FlexAlignResult &res)
{
    res.hingeNum = flexalign_main(
        xa, ya, seqx, seqy, secx, secy,
        res.t0, res.u0, res.tu_vec, res.TM1, res.TM2, res.TM3, res.TM4, res.TM5,
        res.d0_0, res.TM_0, res.d0A, res.d0B, res.d0u, res.d0a, res.d0_out,
        res.seqM, res.seqxA, res.seqyA, res.do_vec,
        res.rmsd0, res.L_ali, res.Liden, res.TM_ali, res.rmsd_ali, res.n_ali, res.n_ali8,
        xlen, ylen, sequence, Lnorm_ass, d0_scale,
        i_opt, a_opt, u_opt, d_opt, force_fast_opt,
        mol_type, hinge_opt, ss_opt);

    // Fallback compensation when too few hinges are found
    if (hinge_opt && res.hingeNum <= 1 && res.n_ali8 < 0.6 * getmin(xlen, ylen))
    {
        FlexAlignResult res_h;
        res_h.tu_vec.push_back(res.tu_vec[0]);
        tu2t_u(res.tu_vec[0], res_h.t0, res_h.u0);

        res_h.hingeNum = flexalign_main(
            xa, ya, seqx, seqy, secx, secy,
            res_h.t0, res_h.u0, res_h.tu_vec,
            res_h.TM1, res_h.TM2, res_h.TM3, res_h.TM4, res_h.TM5,
            res_h.d0_0, res_h.TM_0, res_h.d0A, res_h.d0B, res_h.d0u, res_h.d0a, res_h.d0_out,
            res_h.seqM, res_h.seqxA, res_h.seqyA, res_h.do_vec,
            res_h.rmsd0, res_h.L_ali, res_h.Liden, res_h.TM_ali, res_h.rmsd_ali,
            res_h.n_ali, res_h.n_ali8,
            xlen, ylen, sequence, Lnorm_ass, d0_scale, i_opt,
            a_opt, u_opt, d_opt, force_fast_opt,
            mol_type, hinge_opt, ss_opt);

        double TM = (res.TM1 > res.TM2) ? res.TM1 : res.TM2;
        double TM_h = (res_h.TM1 > res_h.TM2) ? res_h.TM1 : res_h.TM2;
        if (TM_h > TM)
        {
            res = res_h; // Safely overwrite with the better refined results
        }
    }
}

// ==========================================
// USBCAT Core Algorithm (flexalign_usbcat_main)
// ==========================================
struct USBCAT_AFP
{
    int i, j, len;
    double score;
};

int flexalign_usbcat_main(double **xa, double **ya,
                          const char *seqx, const char *seqy, const char *secx, const char *secy,
                          double t0[3], double u0[3][3], std::vector<std::vector<double> > &tu_vec,
                          double &TM1, double &TM2, double &TM3, double &TM4, double &TM5,
                          double &d0_0, double &TM_0,
                          double &d0A, double &d0B, double &d0u, double &d0a, double &d0_out,
                          std::string &seqM, std::string &seqxA, std::string &seqyA, std::vector<double> &do_vec,
                          double &rmsd0, int &L_ali, double &Liden,
                          double &TM_ali, double &rmsd_ali, int &n_ali, int &n_ali8,
                          const int xlen, const int ylen,
                          const std::vector<std::string> sequence, const double Lnorm_ass,
                          const double d0_scale, const int i_opt, const int a_opt,
                          const bool u_opt, const bool d_opt, const bool fast_opt,
                          const int mol_type, const int hinge_opt, const int ss_opt,
                          int sparse_val = 0, bool hinge_set = false, const double TMpass = 0.85)
{
    // ==========================================
    // TRUE flexalign_greedy BASELINE (Defender)
    // Run full sequence without generate_bounds slicing!
    // ==========================================
    double best_global_max_TM = -1.0;
    std::vector<std::vector<double> > best_tu_vec;
    double best_t0[3], best_u0[3][3];
    double best_TM1 = 0.0, best_TM2 = 0.0, best_TM3 = 0.0, best_TM4 = 0.0, best_TM5 = 0.0;
    double best_rmsd0 = 0.0, best_Liden = 0.0, best_TM_ali = 0.0, best_rmsd_ali = 0.0;
    int best_L_ali = 0, best_n_ali = 0, best_n_ali8 = 0;
    std::string best_seqM = "", best_seqxA = "", best_seqyA = "";
    std::vector<double> best_do_vec;
    double best_d0A = 0.0, best_d0B = 0.0, best_d0a = 0.0, best_d0u = 0.0;

    bool force_fast_opt_global = (std::min(xlen, ylen) > 1500) ? true : fast_opt;
    std::vector<std::string> local_sequence = sequence;

    for (int cur_ss_opt = 0; cur_ss_opt <= 1; cur_ss_opt++)
    {
        FlexAlignResult base_res;
        execute_flexalign_with_fallback(
            xa, ya, (char *)seqx, (char *)seqy, (char *)secx, (char *)secy,
            xlen, ylen, local_sequence, Lnorm_ass, d0_scale,
            i_opt, a_opt, u_opt, d_opt, force_fast_opt_global,
            mol_type, hinge_opt, cur_ss_opt, base_res);

        double cur_max_TM = (base_res.TM1 > base_res.TM2) ? base_res.TM1 : base_res.TM2;
        if (cur_max_TM > best_global_max_TM)
        {
            best_global_max_TM = cur_max_TM;
            for (int a = 0; a < 3; a++)
            {
                best_t0[a] = base_res.t0[a];
                for (int b = 0; b < 3; b++)
                    best_u0[a][b] = base_res.u0[a][b];
            }
            best_tu_vec = base_res.tu_vec;
            best_TM1 = base_res.TM1;
            best_TM2 = base_res.TM2;
            best_TM3 = base_res.TM3;
            best_TM4 = base_res.TM4;
            best_TM5 = base_res.TM5;
            best_rmsd0 = base_res.rmsd0;
            best_Liden = base_res.Liden;
            best_TM_ali = base_res.TM_ali;
            best_rmsd_ali = base_res.rmsd_ali;
            best_L_ali = base_res.L_ali;
            best_n_ali = base_res.n_ali;
            best_n_ali8 = base_res.n_ali8;
            best_seqM = base_res.seqM;
            best_seqxA = base_res.seqxA;
            best_seqyA = base_res.seqyA;
            best_do_vec = base_res.do_vec;
            best_d0A = base_res.d0A;
            best_d0B = base_res.d0B;
            best_d0a = base_res.d0a;
            best_d0u = base_res.d0u;
        }
    }

    if (best_global_max_TM >= TMpass)
    {
        TM1 = best_TM1;
        TM2 = best_TM2;
        TM3 = best_TM3;
        TM4 = best_TM4;
        TM5 = best_TM5;
        rmsd0 = best_rmsd0;
        Liden = best_Liden;
        TM_ali = best_TM_ali;
        rmsd_ali = best_rmsd_ali;
        L_ali = best_L_ali;
        n_ali = best_n_ali;
        n_ali8 = best_n_ali8;
        seqM = best_seqM;
        seqxA = best_seqxA;
        seqyA = best_seqyA;
        do_vec = best_do_vec;
        tu_vec = best_tu_vec;
        d0A = best_d0A;
        d0B = best_d0B;
        d0a = best_d0a;
        d0u = best_d0u;
        for (int a = 0; a < 3; a++)
        {
            t0[a] = best_t0[a];
            for (int b = 0; b < 3; b++)
                u0[a][b] = best_u0[a][b];
        }
        return tu_vec.size();
    }

    // ==========================================
    // Proceed to USBCAT sliced bounds logic...
    // ==========================================
    int fragLen = 12;
    double resScore = 3.0;
    double gap_ext = -0.5;
    double disCut = 5.0;
    double disSmooth = 4.0;
    double twist_pen = -fragLen * resScore - 1;
    int max_gap = 40;
    double max_penalty = -5.0 * fragLen / 8.0;
    int misCut = 2 * fragLen;
    int maxGapFrag = fragLen + max_gap;
    double afp_dis_cut = fragLen * fragLen * (disCut * disCut);
    int max_twists = hinge_opt;

    // OPTIMIZATION 1: Precompute local intra-protein distance matrices
    int max_dist_window = max_gap + 2 * fragLen + 1;
    std::vector<std::vector<double> > disTable1(xlen, std::vector<double>(max_dist_window, 0.0));
    std::vector<std::vector<double> > disTable2(ylen, std::vector<double>(max_dist_window, 0.0));

    for (int i = 0; i < xlen; i++)
    {
        for (int j = i; j < std::min(xlen, i + max_dist_window); j++)
            disTable1[i][j - i] = std::sqrt(dist(xa[i], xa[j]));
    }
    for (int i = 0; i < ylen; i++)
    {
        for (int j = i; j < std::min(ylen, i + max_dist_window); j++)
            disTable2[i][j - i] = std::sqrt(dist(ya[i], ya[j]));
    }

    // Wrapper for generating bounds
    auto generate_bounds = [&](double cur_rmsdCut, double cur_badRmsd, double cur_local_badRmsd) -> std::pair<std::vector<int>, std::vector<int> >
    {
        // Step 1: Extract initial AFPs in batches
        std::vector<USBCAT_AFP> initial_afps;
        int step = sparse_val + 1;

        // --- FIXED: Dynamically allocate arrays based on fragLen
        double **r1, **r2;
        NewArray(&r1, fragLen, 3);
        NewArray(&r2, fragLen, 3);

        for (int i = 0; i <= xlen - fragLen; i += step)
        {
            for (int j = 0; j <= ylen - fragLen; j += step)
            {
                int d3_term = std::min(i, j) + std::min(xlen - (i + fragLen - 1), ylen - (j + fragLen)) + fragLen;
                if (d3_term < best_global_max_TM * std::min(xlen, ylen))
                    continue;

                double dist1 = disTable1[i][fragLen - 1];
                double dist2 = disTable2[j][fragLen - 1];

                if (std::fabs(dist1 - dist2) > 2.0 * cur_rmsdCut)
                    continue;

                // k loop dynamically bounds to fragLen instead of fixed 8
                for (int k = 0; k < fragLen; k++)
                {
                    r1[k][0] = xa[i + k][0];
                    r1[k][1] = xa[i + k][1];
                    r1[k][2] = xa[i + k][2];
                    r2[k][0] = ya[j + k][0];
                    r2[k][1] = ya[j + k][1];
                    r2[k][2] = ya[j + k][2];
                }

                double rms_sum_sq, t_tmp[3], u_tmp[3][3];
                // Kabsch function already appropriately takes fragLen as size parameter
                Kabsch(r1, r2, fragLen, 0, &rms_sum_sq, t_tmp, u_tmp);
                double rmsd_tmp = std::sqrt(rms_sum_sq / fragLen);

                if (rmsd_tmp < cur_rmsdCut)
                {
                    USBCAT_AFP afp;
                    afp.i = i;
                    afp.j = j;
                    afp.len = fragLen;
                    afp.score = resScore * fragLen * (1.0 - (rmsd_tmp / cur_badRmsd) * (rmsd_tmp / cur_badRmsd));
                    initial_afps.push_back(afp);
                }
            }
        }

        DeleteArray(&r1, fragLen);
        DeleteArray(&r2, fragLen);

        // Step 2: Merge diagonal AFPs
        int max_diagonal_idx = xlen + ylen + 1;
        std::vector<std::vector<USBCAT_AFP> > diagonals(max_diagonal_idx);
        for (size_t k = 0; k < initial_afps.size(); k++)
        {
            diagonals[initial_afps[k].i - initial_afps[k].j + ylen].push_back(initial_afps[k]);
        }

        std::vector<USBCAT_AFP> merged_afps;
        int max_merge_len = std::min(xlen, ylen);
        double **r1_merge, **r2_merge;
        NewArray(&r1_merge, max_merge_len, 3);
        NewArray(&r2_merge, max_merge_len, 3);

        for (int d = 0; d < max_diagonal_idx; d++)
        {
            if (diagonals[d].empty())
                continue;
            std::vector<USBCAT_AFP> &group = diagonals[d];

            std::sort(group.begin(), group.end(), [](const USBCAT_AFP &a, const USBCAT_AFP &b)
                      { return a.i < b.i; });

            int n_group = group.size();
            std::vector<bool> invalid(n_group, false);
            for (int idx = 0; idx < n_group; idx++)
            {
                if (invalid[idx])
                    continue;
                USBCAT_AFP curr = group[idx];
                for (int nxt_idx = idx + 1; nxt_idx < n_group; nxt_idx++)
                {
                    USBCAT_AFP nxt = group[nxt_idx];
                    if (nxt.i > curr.i + curr.len)
                        break;

                    if (nxt.i + nxt.len > curr.i + curr.len)
                    {
                        int new_len = (nxt.i + nxt.len) - curr.i;
                        for (int k = 0; k < new_len; k++)
                        {
                            r1_merge[k][0] = xa[curr.i + k][0];
                            r1_merge[k][1] = xa[curr.i + k][1];
                            r1_merge[k][2] = xa[curr.i + k][2];
                            r2_merge[k][0] = ya[curr.j + k][0];
                            r2_merge[k][1] = ya[curr.j + k][1];
                            r2_merge[k][2] = ya[curr.j + k][2];
                        }

                        double rms_sum_sq, t_tmp[3], u_tmp[3][3];
                        Kabsch(r1_merge, r2_merge, new_len, 0, &rms_sum_sq, t_tmp, u_tmp);
                        double rmsd_tmp = std::sqrt(rms_sum_sq / new_len);

                        if (rmsd_tmp < cur_rmsdCut)
                        {
                            curr.len = new_len;
                            curr.score = resScore * new_len * (1.0 - (rmsd_tmp / cur_badRmsd) * (rmsd_tmp / cur_badRmsd));
                            invalid[nxt_idx] = true;
                        }
                    }
                }
                merged_afps.push_back(curr);
            }
        }
        DeleteArray(&r1_merge, max_merge_len);
        DeleteArray(&r2_merge, max_merge_len);

        std::sort(merged_afps.begin(), merged_afps.end(), [](const USBCAT_AFP &a, const USBCAT_AFP &b)
                  {
            if (a.i == b.i) return a.j < b.j;
            return a.i < b.i; });

        int n_afps = merged_afps.size();
        std::vector<int> ret_b1, ret_b2;
        if (n_afps == 0)
            return std::make_pair(ret_b1, ret_b2);

        // Step 3 & 4: Dual Dynamic Programming and Domain Splitting
        std::vector<int> afp_aft_index(xlen * ylen, -1);
        std::vector<int> afp_bef_index(xlen * ylen, -1);

        std::vector<std::vector<std::pair<int, int> > > i_to_j(xlen);
        for (int m = 0; m < n_afps; m++)
        {
            i_to_j[merged_afps[m].i].push_back(std::make_pair(merged_afps[m].j, m));
        }

        for (int i_val = 0; i_val < xlen; i_val++)
        {
            if (i_to_j[i_val].empty())
                continue;
            for (size_t p = 0; p < i_to_j[i_val].size(); p++)
            {
                int j_val = i_to_j[i_val][p].first;
                afp_aft_index[i_val * ylen + j_val] = i_to_j[i_val][p].second;
                afp_bef_index[i_val * ylen + j_val] = i_to_j[i_val][p].second;
            }
            int curr_bef = -1;
            for (int j_val = 0; j_val < ylen; j_val++)
            {
                if (afp_bef_index[i_val * ylen + j_val] != -1)
                    curr_bef = afp_bef_index[i_val * ylen + j_val];
                else
                    afp_bef_index[i_val * ylen + j_val] = curr_bef;
            }
            int curr_aft = -1;
            for (int j_val = ylen - 1; j_val >= 0; j_val--)
            {
                if (afp_aft_index[i_val * ylen + j_val] != -1)
                    curr_aft = afp_aft_index[i_val * ylen + j_val];
                else
                    afp_aft_index[i_val * ylen + j_val] = curr_aft;
            }
        }

        auto get_dvar = [&](const USBCAT_AFP &prv, const USBCAT_AFP &curr) -> double
        {
            double rms_sq = 0;
            for (int i_idx = 0; i_idx < fragLen; i_idx++)
            {
                for (int j_idx = 0; j_idx < fragLen; j_idx++)
                {
                    double dist1, dist2;
                    int idx1_a = curr.i + i_idx, idx1_b = prv.i + j_idx;
                    if (idx1_a >= idx1_b)
                        dist1 = disTable1[idx1_b][idx1_a - idx1_b];
                    else
                        dist1 = disTable1[idx1_a][idx1_b - idx1_a];

                    int idx2_a = curr.j + i_idx, idx2_b = prv.j + j_idx;
                    if (idx2_a >= idx2_b)
                        dist2 = disTable2[idx2_b][idx2_a - idx2_b];
                    else
                        dist2 = disTable2[idx2_a][idx2_b - idx2_a];

                    rms_sq += (dist1 - dist2) * (dist1 - dist2);
                }
            }
            if (rms_sq > afp_dis_cut)
                return 1e9;
            return std::sqrt(rms_sq / (fragLen * fragLen));
        };

        auto calc_block_rmsd = [&](const std::vector<USBCAT_AFP> &afp_list) -> double
        {
            std::vector<int> r1, r2;
            for (size_t a = 0; a < afp_list.size(); a++)
            {
                for (int l = 0; l < afp_list[a].len; l++)
                {
                    r1.push_back(afp_list[a].i + l);
                    r2.push_back(afp_list[a].j + l);
                }
            }
            int n = r1.size();
            if (n < 3)
                return 0.0;
            double **p1;
            NewArray(&p1, n, 3);
            double **p2;
            NewArray(&p2, n, 3);
            for (int i = 0; i < n; i++)
            {
                p1[i][0] = xa[r1[i]][0];
                p1[i][1] = xa[r1[i]][1];
                p1[i][2] = xa[r1[i]][2];
                p2[i][0] = ya[r2[i]][0];
                p2[i][1] = ya[r2[i]][1];
                p2[i][2] = ya[r2[i]][2];
            }
            double rms_sq_sum, t_tmp[3], u_tmp[3][3];
            Kabsch(p1, p2, n, 0, &rms_sq_sum, t_tmp, u_tmp);
            DeleteArray(&p1, n);
            DeleteArray(&p2, n);
            return std::sqrt(rms_sq_sum / n);
        };

        struct Region
        {
            int s1, e1, s2, e2;
        };

        std::vector<double> sco(n_afps);
        std::vector<int> twi(n_afps, 0);
        std::vector<int> pre(n_afps, -1);
        for (int m = 0; m < n_afps; m++)
            sco[m] = merged_afps[m].score;

        for (int m = 0; m < n_afps; m++)
        {
            int curr_i = merged_afps[m].i;
            int curr_j = merged_afps[m].j;
            int a3 = curr_i - fragLen;
            int a2 = std::max(0, a3 - misCut);
            int a1 = std::max(0, curr_i - maxGapFrag);
            int b3 = curr_j - fragLen;
            int b2 = std::max(0, b3 - misCut);
            int b1 = std::max(0, curr_j - maxGapFrag);

            std::vector<int> valid_prevs;
            for (int st = 0; st < 2; st++)
            {
                int a_s, a_e, b_s, b_e;
                if (st == 0)
                {
                    a_s = std::max(a1, 0);
                    a_e = std::min(a3, xlen - 1);
                    b_s = std::max(b2, 0);
                    b_e = std::min(b3, ylen - 1);
                }
                else
                {
                    a_s = std::max(a2, 0);
                    a_e = std::min(a3, xlen - 1);
                    b_s = std::max(b1, 0);
                    b_e = std::min(b2 - 1, ylen - 1);
                }

                if (b_s >= ylen || b_e < 0)
                    continue;
                for (int prev_i = a_s; prev_i <= a_e; prev_i++)
                {
                    int s1 = afp_aft_index[prev_i * ylen + b_s];
                    int s2 = afp_bef_index[prev_i * ylen + b_e];
                    if (s1 != -1 && s2 != -1 && s1 <= s2)
                        for (int s = s1; s <= s2; s++)
                            valid_prevs.push_back(s);
                }
            }

            double curr_sco = merged_afps[m].score;
            for (size_t v = 0; v < valid_prevs.size(); v++)
            {
                int prev = valid_prevs[v];
                int prev_twi = twi[prev];
                if (prev_twi > max_twists)
                    continue;

                int gap_i = curr_i - (merged_afps[prev].i + merged_afps[prev].len);
                int gap_j = curr_j - (merged_afps[prev].j + merged_afps[prev].len);
                int m_gap = std::max(gap_i, gap_j);

                double gp = 0.0;
                int m_mis = 0;
                if (gap_i < 0 || gap_j < 0)
                    m_mis = (gap_i < gap_j) ? -gap_i : -gap_j;
                gp = gap_ext * m_mis;
                if (m_gap > 0)
                    gp += gap_ext * m_gap;
                if (gp < max_penalty)
                    gp = max_penalty;

                double rms_sq = 0;
                for (int k = 0; k < fragLen; k++)
                {
                    for (int l = 0; l < fragLen; l++)
                    {
                        double dist1, dist2;
                        int idx1_a = curr_i + k, idx1_b = merged_afps[prev].i + l;
                        if (idx1_a >= idx1_b)
                            dist1 = disTable1[idx1_b][idx1_a - idx1_b];
                        else
                            dist1 = disTable1[idx1_a][idx1_b - idx1_a];

                        int idx2_a = curr_j + k, idx2_b = merged_afps[prev].j + l;
                        if (idx2_a >= idx2_b)
                            dist2 = disTable2[idx2_b][idx2_a - idx2_b];
                        else
                            dist2 = disTable2[idx2_a][idx2_b - idx2_a];

                        rms_sq += (dist1 - dist2) * (dist1 - dist2);
                    }
                }

                double tp = 0.0;
                int is_twist = 0;
                if (rms_sq >= afp_dis_cut)
                {
                    tp = twist_pen;
                    is_twist = 1;
                }
                else
                {
                    double dvar = std::sqrt(rms_sq / (fragLen * fragLen));
                    if (dvar > disCut - disSmooth)
                        tp = twist_pen * std::sqrt((dvar - disCut + disSmooth) / disSmooth);
                }

                if (prev_twi + is_twist > max_twists)
                    continue;

                double stmp = sco[prev] + curr_sco + tp + gp;
                if (stmp > sco[m])
                {
                    sco[m] = stmp;
                    pre[m] = prev;
                    twi[m] = prev_twi + is_twist;
                }
            }
        }

        int best_m = 0;
        for (int m = 1; m < n_afps; m++)
            if (sco[m] > sco[best_m])
                best_m = m;

        std::vector<int> path;
        int curr_m = best_m;
        while (curr_m != -1)
        {
            path.push_back(curr_m);
            curr_m = pre[curr_m];
        }
        std::reverse(path.begin(), path.end());

        if (path.empty())
            return std::make_pair(ret_b1, ret_b2);

        struct Block
        {
            std::vector<USBCAT_AFP> afps;
            std::vector<double> dvars;
        };
        std::vector<Block> candidate_blocks;
        Block curr_block;
        curr_block.afps.push_back(merged_afps[path[0]]);
        curr_block.dvars.push_back(0.0);

        for (size_t k = 1; k < path.size(); k++)
        {
            USBCAT_AFP curr = merged_afps[path[k]];
            USBCAT_AFP prv = merged_afps[path[k - 1]];
            double dvar = get_dvar(prv, curr);

            if (dvar >= disCut)
            {
                candidate_blocks.push_back(curr_block);
                curr_block.afps.clear();
                curr_block.dvars.clear();
                curr_block.afps.push_back(curr);
                curr_block.dvars.push_back(0.0);
            }
            else
            {
                curr_block.afps.push_back(curr);
                curr_block.dvars.push_back(dvar);
            }
        }
        if (!curr_block.afps.empty())
            candidate_blocks.push_back(curr_block);

        bool splitted = true;
        while (splitted && candidate_blocks.size() < (size_t)(max_twists + 1))
        {
            splitted = false;
            double max_rmsd = 0.0;
            int target_b = -1;

            for (size_t b = 0; b < candidate_blocks.size(); b++)
            {
                if (candidate_blocks[b].afps.size() > 2)
                {
                    double cur_rmsd = calc_block_rmsd(candidate_blocks[b].afps);
                    if (cur_rmsd > max_rmsd)
                    {
                        max_rmsd = cur_rmsd;
                        target_b = b;
                    }
                }
            }

            if (max_rmsd >= cur_local_badRmsd && target_b != -1)
            {
                double max_t = 0;
                int cut_idx = 0;
                for (size_t i = 1; i < candidate_blocks[target_b].afps.size(); i++)
                {
                    if (candidate_blocks[target_b].dvars[i] > max_t)
                    {
                        max_t = candidate_blocks[target_b].dvars[i];
                        cut_idx = i;
                    }
                }

                if (cut_idx > 0)
                {
                    Block right_blk;
                    right_blk.afps.assign(candidate_blocks[target_b].afps.begin() + cut_idx, candidate_blocks[target_b].afps.end());
                    right_blk.dvars.assign(candidate_blocks[target_b].dvars.begin() + cut_idx, candidate_blocks[target_b].dvars.end());
                    right_blk.dvars[0] = 0.0;
                    candidate_blocks[target_b].afps.erase(candidate_blocks[target_b].afps.begin() + cut_idx, candidate_blocks[target_b].afps.end());
                    candidate_blocks[target_b].dvars.erase(candidate_blocks[target_b].dvars.begin() + cut_idx, candidate_blocks[target_b].dvars.end());
                    candidate_blocks.insert(candidate_blocks.begin() + target_b + 1, right_blk);
                    splitted = true;
                }
            }
        }

        for (int b = 0; b < (int)candidate_blocks.size(); b++)
        {
            if (candidate_blocks[b].afps.size() <= 1)
            {
                int e1 = (b < (int)candidate_blocks.size() - 1) ? candidate_blocks[b + 1].afps.front().i : xlen;
                int e2 = (b < (int)candidate_blocks.size() - 1) ? candidate_blocks[b + 1].afps.front().j : ylen;
                int b1 = (b > 0) ? candidate_blocks[b - 1].afps.back().i + candidate_blocks[b - 1].afps.back().len : 0;
                int b2 = (b > 0) ? candidate_blocks[b - 1].afps.back().j + candidate_blocks[b - 1].afps.back().len : 0;
                int span = std::min(e1 - b1, e2 - b2);
                if (span < 2 * fragLen)
                {
                    candidate_blocks.erase(candidate_blocks.begin() + b);
                    b--;
                }
            }
        }

        bool merged = true;
        while (merged && candidate_blocks.size() > 1)
        {
            merged = false;
            double min_rmsd = 1e9;
            int min_b = -1;
            for (size_t b = 0; b < candidate_blocks.size() - 1; b++)
            {
                std::vector<USBCAT_AFP> temp_merged = candidate_blocks[b].afps;
                temp_merged.insert(temp_merged.end(), candidate_blocks[b + 1].afps.begin(), candidate_blocks[b + 1].afps.end());
                double cur_rmsd = calc_block_rmsd(temp_merged);
                if (cur_rmsd < min_rmsd)
                {
                    min_rmsd = cur_rmsd;
                    min_b = b;
                }
            }

            if (min_rmsd < cur_local_badRmsd && min_b != -1)
            {
                candidate_blocks[min_b].afps.insert(candidate_blocks[min_b].afps.end(), candidate_blocks[min_b + 1].afps.begin(), candidate_blocks[min_b + 1].afps.end());
                candidate_blocks.erase(candidate_blocks.begin() + min_b + 1);
                merged = true;
            }
        }

        std::vector<Region> usbcat_domains;
        int last_i = 0, last_j = 0;
        for (size_t b = 0; b < candidate_blocks.size(); b++)
        {
            int b_s1 = -1, b_e1 = -1, b_s2 = -1, b_e2 = -1;
            for (size_t a = 0; a < candidate_blocks[b].afps.size(); a++)
            {
                USBCAT_AFP afp = candidate_blocks[b].afps[a];
                int skip = std::max(std::max(last_i - afp.i, last_j - afp.j), 0);
                if (skip >= afp.len)
                    continue;

                int eff_i = afp.i + skip;
                int eff_j = afp.j + skip;
                int eff_L = afp.len - skip;
                if (b_s1 == -1)
                {
                    b_s1 = eff_i;
                    b_s2 = eff_j;
                }
                b_e1 = eff_i + eff_L;
                b_e2 = eff_j + eff_L;
                last_i = b_e1;
                last_j = b_e2;
            }
            if (b_s1 != -1)
            {
                if (b_e1 - b_s1 >= 4 && b_e2 - b_s2 >= 4)
                {
                    Region r = {b_s1, b_e1, b_s2, b_e2};
                    usbcat_domains.push_back(r);
                }
            }
        }

        if (usbcat_domains.empty())
            return std::make_pair(ret_b1, ret_b2);

        ret_b1.push_back(0);
        ret_b2.push_back(0);
        for (size_t k = 0; k < usbcat_domains.size() - 1; k++)
        {
            ret_b1.push_back((usbcat_domains[k].e1 + usbcat_domains[k + 1].s1) / 2);
            ret_b2.push_back((usbcat_domains[k].e2 + usbcat_domains[k + 1].s2) / 2);
        }
        ret_b1.push_back(xlen);
        ret_b2.push_back(ylen);

        return std::make_pair(ret_b1, ret_b2);
    };

    auto bounds_default = generate_bounds(3.0, 4.0, 4.0);
    auto bounds_strict = generate_bounds(2.0, 3.0, 2.0);

    std::vector<std::pair<std::vector<int>, std::vector<int> > > all_bounds;
    all_bounds.push_back(bounds_default);
    if (bounds_strict.first != bounds_default.first || bounds_strict.second != bounds_default.second)
    {
        all_bounds.push_back(bounds_strict);
    }

    for (size_t b_idx = 0; b_idx < all_bounds.size(); b_idx++)
    {
        std::vector<int> &bounds1 = all_bounds[b_idx].first;
        std::vector<int> &bounds2 = all_bounds[b_idx].second;

        // Skip if only one interval (block) is generated
        if (bounds1.size() <= 2)
            continue;

        // =========================================================================
        // Greedy Dynamic Hinge Budgeting based on dRMSD
        // =========================================================================
        int num_blocks = bounds1.size() - 1;

        // 1. Define execution node for out-of-order processing
        struct BlockMeta
        {
            int original_idx;
            double drmsd;
            int L1_sub, L2_sub;
        };
        std::vector<BlockMeta> block_queue;

        // 2. Calculate proxy dRMSD for each block to evaluate internal strain energy
        for (int k = 0; k < num_blocks; k++)
        {
            int x_s = bounds1[k], x_e = bounds1[k + 1];
            int y_s = bounds2[k], y_e = bounds2[k + 1];
            int L1_sub = x_e - x_s;
            int L2_sub = y_e - y_s;
            int min_L = std::min(L1_sub, L2_sub);

            double block_drmsd = 0.0;
            // Only calculate if the block is long enough
            if (min_L >= 2 * fragLen)
            {
                double rms_sq = 0.0;
                int count = 0;
                for (int i = 0; i < min_L; i++)
                {
                    // DOUBLE OPTIMIZATION:
                    // 1. Start from i + 2 to skip adjacent amino acids (peptide bond noise).
                    // 2. Cap j at i + max_dist_window to SAFELY reuse precomputed disTable!
                    //    This perfectly evaluates "local" strain energy and reduces time complexity to O(N).
                    int j_end = std::min((int)min_L, i + max_dist_window);

                    for (int j = i + 2; j < j_end; j++)
                    {
                        // Directly query the precomputed distance tables
                        double d1 = disTable1[x_s + i][j - i];
                        double d2 = disTable2[y_s + i][j - i];
                        rms_sq += (d1 - d2) * (d1 - d2);
                        count++;
                    }
                }
                if (count > 0)
                    block_drmsd = std::sqrt(rms_sq / count);
            }
            block_queue.push_back({k, block_drmsd, L1_sub, L2_sub});
        }

        // 3. Sort blocks by dRMSD descending (most twisted blocks get priority)
        if (hinge_set)
        {
            std::sort(block_queue.begin(), block_queue.end(), [](const BlockMeta &a, const BlockMeta &b)
                      { return a.drmsd > b.drmsd; });
        }

        // 4. Initialize global hinge pool
        // N blocks intrinsically use N-1 cut points, remaining hinges = hinge_opt + 1 - N
        int remaining_hinges = hinge_set ? std::max(0, hinge_opt + 1 - num_blocks) : 0;

        // Structure to store out-of-order execution results
        struct BlockResult
        {
            bool valid;
            double t0[3];
            double u0[3][3];
            std::string seqM, seqxA, seqyA;
            std::vector<std::vector<double> > tu_vec;
            BlockResult() : valid(false) {}
        };
        std::vector<BlockResult> block_results(num_blocks);

        // 5. Greedy execution: Allocate all available hinges to the current most twisted block
        for (size_t q = 0; q < block_queue.size(); q++)
        {
            int k = block_queue[q].original_idx;
            int L1_sub = block_queue[q].L1_sub;
            int L2_sub = block_queue[q].L2_sub;
            int x_s = bounds1[k], y_s = bounds2[k];

            // Skip invalid or too short blocks
            if (L1_sub < 3 || L2_sub < 3)
                continue;

            // CORE LOGIC: Pass all remaining budget to the current block
            int local_hinge_opt = 0;
            if (hinge_set)
            {
                if (remaining_hinges > 0 && std::min(L1_sub, L2_sub) >= 2 * fragLen)
                {
                    local_hinge_opt = remaining_hinges;
                }
            }
            else
            {
                // If -hinge is not set, allocate 2 hinges per block by default
                // This allows a maximum of (9+1)*(2+1)=30 aligned intervals
                if (std::min(L1_sub, L2_sub) >= 2 * fragLen)
                {
                    local_hinge_opt = 2;
                }
            }

            // Reuse variables from the original logic for sub-block allocation
            double **xa_sub, **ya_sub;
            NewArray(&xa_sub, L1_sub, 3);
            NewArray(&ya_sub, L2_sub, 3);
            char *seqx_sub = new char[L1_sub + 1];
            char *secx_sub = new char[L1_sub + 1];
            char *seqy_sub = new char[L2_sub + 1];
            char *secy_sub = new char[L2_sub + 1];

            for (int i = 0; i < L1_sub; i++)
            {
                xa_sub[i][0] = xa[x_s + i][0];
                xa_sub[i][1] = xa[x_s + i][1];
                xa_sub[i][2] = xa[x_s + i][2];
                seqx_sub[i] = seqx[x_s + i];
                secx_sub[i] = secx[x_s + i];
            }
            seqx_sub[L1_sub] = '\0';
            secx_sub[L1_sub] = '\0';

            for (int i = 0; i < L2_sub; i++)
            {
                ya_sub[i][0] = ya[y_s + i][0];
                ya_sub[i][1] = ya[y_s + i][1];
                ya_sub[i][2] = ya[y_s + i][2];
                seqy_sub[i] = seqy[y_s + i];
                secy_sub[i] = secy[y_s + i];
            }
            seqy_sub[L2_sub] = '\0';
            secy_sub[L2_sub] = '\0';

            bool force_fast_opt = (std::min(L1_sub, L2_sub) > 1500) ? true : fast_opt;
            double TM_best_max = -1.0;

            // Try both secondary structure configurations
            for (int cur_ss_opt = 0; cur_ss_opt <= 1; cur_ss_opt++)
            {
                FlexAlignResult cur_res;
                execute_flexalign_with_fallback(
                    xa_sub, ya_sub, seqx_sub, seqy_sub, secx_sub, secy_sub,
                    L1_sub, L2_sub, local_sequence, Lnorm_ass, d0_scale,
                    i_opt, a_opt, u_opt, d_opt, force_fast_opt,
                    mol_type, local_hinge_opt, cur_ss_opt, cur_res);

                double cur_max_TM = (cur_res.TM1 > cur_res.TM2) ? cur_res.TM1 : cur_res.TM2;
                if (cur_max_TM > TM_best_max)
                {
                    TM_best_max = cur_max_TM;
                    for (int a = 0; a < 3; a++)
                    {
                        block_results[k].t0[a] = cur_res.t0[a];
                        for (int b = 0; b < 3; b++)
                            block_results[k].u0[a][b] = cur_res.u0[a][b];
                    }
                    block_results[k].seqM = cur_res.seqM;
                    block_results[k].seqxA = cur_res.seqxA;
                    block_results[k].seqyA = cur_res.seqyA;
                    block_results[k].tu_vec = cur_res.tu_vec;
                    block_results[k].valid = true;
                }
            }

            // Deduct actually consumed hinges from the global budget
            if (hinge_set && block_results[k].valid && !block_results[k].tu_vec.empty())
            {
                int consumed_hinges = block_results[k].tu_vec.size() - 1;
                if (consumed_hinges > 0)
                {
                    remaining_hinges -= consumed_hinges;
                    if (remaining_hinges < 0)
                        remaining_hinges = 0; // Guard against negative budget
                }
            }

            // Clean up sub-block memory
            DeleteArray(&xa_sub, L1_sub);
            DeleteArray(&ya_sub, L2_sub);
            delete[] seqx_sub;
            delete[] seqy_sub;
            delete[] secx_sub;
            delete[] secy_sub;
        }

        // 6. Chronological Stitching: Reassemble results in spatial sequence order
        std::string cur_global_seqM = "", cur_global_seqxA = "", cur_global_seqyA = "";
        std::vector<std::vector<double> > cur_tu_vec;
        std::vector<int> cur_global_res_tu(xlen, -1);

        for (int k = 0; k < num_blocks; k++)
        {
            int L1_sub = bounds1[k + 1] - bounds1[k];
            int L2_sub = bounds2[k + 1] - bounds2[k];

            if (!block_results[k].valid)
            {
                // Fill gaps if block was invalid or bypassed
                for (int i = 0; i < L1_sub; i++)
                {
                    cur_global_seqxA += seqx[bounds1[k] + i];
                    cur_global_seqyA += '-';
                    cur_global_seqM += ' ';
                }
                for (int i = 0; i < L2_sub; i++)
                {
                    cur_global_seqxA += '-';
                    cur_global_seqyA += seqy[bounds2[k] + i];
                    cur_global_seqM += ' ';
                }
                continue;
            }

            BlockResult &res = block_results[k];
            if (res.tu_vec.empty())
            {
                std::vector<double> tu_tmp(12);
                t_u2tu(res.t0, res.u0, tu_tmp);
                res.tu_vec.push_back(tu_tmp);
            }

            int base_tu_idx = cur_tu_vec.size();
            for (size_t m = 0; m < res.tu_vec.size(); m++)
                cur_tu_vec.push_back(res.tu_vec[m]);

            int rx = bounds1[k];
            int current_global_idx = base_tu_idx;

            for (size_t i = 0; i < res.seqxA.length(); i++)
            {
                char c = res.seqM[i];
                if (c != ' ' && c != '.' && c != ':')
                {
                    int local_hinge_idx = -1;
                    if (c >= '0' && c <= '9')
                        local_hinge_idx = c - '0';
                    else if (c >= 'a' && c <= 'z')
                        local_hinge_idx = c - 'a' + 10;
                    else if (c >= 'A' && c <= 'Z')
                        local_hinge_idx = c - 'A' + 36;

                    if (local_hinge_idx >= 0 && local_hinge_idx < res.tu_vec.size())
                    {
                        current_global_idx = base_tu_idx + local_hinge_idx;
                    }
                }

                if (res.seqxA[i] != '-')
                {
                    cur_global_res_tu[rx] = current_global_idx;
                    rx++;
                }

                if (res.seqxA[i] != '-' && res.seqyA[i] != '-')
                {
                    if (c != ' ' && c != '.' && c != ':')
                    {
                        char global_c;
                        if (current_global_idx < 10)
                            global_c = '0' + current_global_idx;
                        else if (current_global_idx < 36)
                            global_c = 'a' + (current_global_idx - 10);
                        else if (current_global_idx < 62)
                            global_c = 'A' + (current_global_idx - 36);
                        else
                            global_c = '*';
                        res.seqM[i] = global_c;
                    }
                    else
                    {
                        res.seqM[i] = c;
                    }
                }
                else
                {
                    res.seqM[i] = ' ';
                }
            }

            cur_global_seqM += res.seqM;
            cur_global_seqxA += res.seqxA;
            cur_global_seqyA += res.seqyA;
        }

        // Step 7: Recalculate global metrics correctly for current DP boundary
        double dummy_D0_MIN, dummy_Lnorm, dummy_d0_search;
        double cur_d0A, cur_d0B, cur_d0a, cur_d0u = 0.0;

        parameter_set4final(ylen, dummy_D0_MIN, dummy_Lnorm, cur_d0A, dummy_d0_search, mol_type);
        parameter_set4final(xlen, dummy_D0_MIN, dummy_Lnorm, cur_d0B, dummy_d0_search, mol_type);
        parameter_set4final((xlen + ylen) * 0.5, dummy_D0_MIN, dummy_Lnorm, cur_d0a, dummy_d0_search, mol_type);

        if (u_opt)
        {
            parameter_set4final(Lnorm_ass, dummy_D0_MIN, dummy_Lnorm, cur_d0u, dummy_d0_search, mol_type);
        }

        double cur_TM1 = 0.0, cur_TM2 = 0.0, cur_TM3 = 0.0, cur_TM4 = 0.0, cur_TM5 = 0.0;
        double cur_rmsd0 = 0.0, cur_Liden = 0.0;
        int cur_n_ali8 = 0, cur_n_ali = 0;
        std::vector<double> cur_do_vec;

        int i_res = 0, j_res = 0;
        for (size_t r = 0; r < cur_global_seqxA.length(); r++)
        {
            bool x_valid = (cur_global_seqxA[r] != '-');
            bool y_valid = (cur_global_seqyA[r] != '-');

            if (x_valid && y_valid)
            {
                int matrix_idx = cur_global_res_tu[i_res];
                if (matrix_idx >= 0 && matrix_idx < cur_tu_vec.size())
                {
                    double t_k[3], u_k[3][3];
                    tu2t_u(cur_tu_vec[matrix_idx], t_k, u_k);

                    double x_rot[3];
                    transform(t_k, u_k, xa[i_res], x_rot);
                    double dist2 = dist(x_rot, ya[j_res]);
                    double d = std::sqrt(dist2);

                    cur_TM2 += 1.0 / (1.0 + dist2 / (cur_d0B * cur_d0B));
                    cur_TM1 += 1.0 / (1.0 + dist2 / (cur_d0A * cur_d0A));
                    if (a_opt)
                        cur_TM3 += 1.0 / (1.0 + dist2 / (cur_d0a * cur_d0a));
                    if (u_opt)
                        cur_TM4 += 1.0 / (1.0 + dist2 / (cur_d0u * cur_d0u));
                    if (d_opt)
                        cur_TM5 += 1.0 / (1.0 + dist2 / (d0_scale * d0_scale));

                    cur_n_ali++;
                    cur_do_vec.push_back(d);

                    if (d <= d0_out)
                    {
                        cur_rmsd0 += dist2;
                        cur_n_ali8++;
                        if (seqx[i_res] == seqy[j_res])
                            cur_Liden += 1.0;
                    }
                }
                else
                {
                    cur_do_vec.push_back(-1);
                }
            }
            else
            {
                cur_do_vec.push_back(-1);
            }

            if (x_valid)
                i_res++;
            if (y_valid)
                j_res++;
        }

        cur_TM2 /= xlen;
        cur_TM1 /= ylen;
        if (a_opt)
            cur_TM3 /= (xlen + ylen) * 0.5;
        if (u_opt)
            cur_TM4 /= Lnorm_ass;
        if (d_opt)
            cur_TM5 /= ylen;
        if (cur_n_ali8 > 0)
            cur_rmsd0 = std::sqrt(cur_rmsd0 / cur_n_ali8);
        else
            cur_rmsd0 = 0.0;

        double cur_global_max_TM = (cur_TM1 > cur_TM2) ? cur_TM1 : cur_TM2;

        if (cur_global_max_TM > best_global_max_TM)
        {
            best_global_max_TM = cur_global_max_TM;
            best_tu_vec = cur_tu_vec;
            best_TM1 = cur_TM1;
            best_TM2 = cur_TM2;
            best_TM3 = cur_TM3;
            best_TM4 = cur_TM4;
            best_TM5 = cur_TM5;
            best_rmsd0 = cur_rmsd0;
            best_Liden = cur_Liden;
            best_TM_ali = cur_TM1;
            best_rmsd_ali = cur_rmsd0;
            best_L_ali = cur_n_ali;
            best_n_ali = cur_n_ali;
            best_n_ali8 = cur_n_ali8;
            best_seqM = cur_global_seqM;
            best_seqxA = cur_global_seqxA;
            best_seqyA = cur_global_seqyA;
            best_do_vec = cur_do_vec;
            best_d0A = cur_d0A;
            best_d0B = cur_d0B;
            best_d0a = cur_d0a;
            best_d0u = cur_d0u;

            if (!best_tu_vec.empty())
            {
                tu2t_u(best_tu_vec[0], best_t0, best_u0);
            }
        }
    }

    // Safety check
    if (best_global_max_TM < 0)
        return 0;

    // Output best values back to the reference parameters
    TM1 = best_TM1;
    TM2 = best_TM2;
    TM3 = best_TM3;
    TM4 = best_TM4;
    TM5 = best_TM5;
    rmsd0 = best_rmsd0;
    Liden = best_Liden;
    TM_ali = best_TM_ali;
    rmsd_ali = best_rmsd_ali;
    L_ali = best_L_ali;
    n_ali = best_n_ali;
    n_ali8 = best_n_ali8;
    seqM = best_seqM;
    seqxA = best_seqxA;
    seqyA = best_seqyA;
    do_vec = best_do_vec;
    tu_vec = best_tu_vec;
    d0A = best_d0A;
    d0B = best_d0B;
    d0a = best_d0a;
    d0u = best_d0u;

    for (int a = 0; a < 3; a++)
    {
        t0[a] = best_t0[a];
        for (int b = 0; b < 3; b++)
            u0[a][b] = best_u0[a][b];
    }

    return tu_vec.size();
}
#endif

// ===== END flexalign.h =====

// Unified engine replacing flexalign_greedy and flexalign_usbcat
int flexalign_unified(string &xname, string &yname, const string &fname_super,
                      const string &fname_lign, const string &fname_matrix,
                      vector<string> &sequence, const double Lnorm_ass, const double d0_scale,
                      const bool m_opt, const int i_opt, const int o_opt, const int a_opt,
                      const bool u_opt, const bool d_opt, const double TMcut,
                      const int infmt1_opt, const int infmt2_opt, const int ter_opt,
                      const int split_opt, const int outfmt_opt, const bool fast_opt,
                      const int mirror_opt, const int het_opt, const string &atom_opt,
                      const bool autojustify, const string &mol_opt, const string &dir_opt,
                      const string &dirpair_opt, const string &dir1_opt, const string &dir2_opt,
                      const vector<string> &chain2parse1, const vector<string> &chain2parse2,
                      const vector<string> &model2parse1, const vector<string> &model2parse2,
                      const int byresi_opt, const vector<string> &chain1_list,
                      const vector<string> &chain2_list, const int hinge_opt, const int ss_opt,
                      FlexAlignMode mode = FLEX_STANDARD, bool hinge_set = false, double TMpass = 0.85)
{
    vector<vector<string> > PDB_lines1;
    vector<vector<string> > PDB_lines2;
    vector<int> mol_vec1;
    vector<int> mol_vec2;
    vector<string> chainID_list1;
    vector<string> chainID_list2;
    int i, j, chain_i, chain_j, r, xlen, ylen, xchainnum, ychainnum;
    char *seqx, *seqy, *secx, *secy;
    double **xa, **ya;
    vector<string> resi_vec1;
    vector<string> resi_vec2;
    int read_resi = byresi_opt;
    if (byresi_opt == 0 && o_opt)
        read_resi = 2;

    for (i = 0; i < chain1_list.size(); i++)
    {
        xname = chain1_list[i];
        xchainnum = get_PDB_lines(xname, PDB_lines1, chainID_list1,
                                  mol_vec1, ter_opt, infmt1_opt, atom_opt, autojustify,
                                  split_opt, het_opt, chain2parse1, model2parse1);
        if (!xchainnum)
        {
            cerr << "Warning! Cannot parse file: " << xname << ". Chain number 0." << endl;
            continue;
        }
        for (chain_i = 0; chain_i < xchainnum; chain_i++)
        {
            xlen = PDB_lines1[chain_i].size();
            if (mol_opt == "RNA")
                mol_vec1[chain_i] = 1;
            else if (mol_opt == "protein")
                mol_vec1[chain_i] = -1;
            if (xlen < 3)
                continue;

            NewArray(&xa, xlen, 3);
            seqx = new char[xlen + 1];
            secx = new char[xlen + 1];
            read_PDB(PDB_lines1[chain_i], xa, seqx, resi_vec1, read_resi);
            if (mirror_opt)
                for (r = 0; r < xlen; r++)
                    xa[r][2] = -xa[r][2];
            (mol_vec1[chain_i] > 0) ? make_sec(seqx, xa, xlen, secx, atom_opt) : make_sec(xa, xlen, secx);

            for (j = (dir_opt.size() > 0) * (i + 1); j < chain2_list.size(); j++)
            {
                if (dirpair_opt.size() && i != j)
                    continue;
                if (PDB_lines2.size() == 0)
                {
                    yname = chain2_list[j];
                    ychainnum = get_PDB_lines(yname, PDB_lines2, chainID_list2,
                                              mol_vec2, ter_opt, infmt2_opt, atom_opt, autojustify,
                                              split_opt, het_opt, chain2parse2, model2parse2);
                    if (!ychainnum)
                        continue;
                }
                for (chain_j = 0; chain_j < ychainnum; chain_j++)
                {
                    ylen = PDB_lines2[chain_j].size();
                    if (mol_opt == "RNA")
                        mol_vec2[chain_j] = 1;
                    else if (mol_opt == "protein")
                        mol_vec2[chain_j] = -1;
                    if (ylen < 3)
                        continue;

                    NewArray(&ya, ylen, 3);
                    seqy = new char[ylen + 1];
                    secy = new char[ylen + 1];
                    read_PDB(PDB_lines2[chain_j], ya, seqy, resi_vec2, read_resi);
                    (mol_vec2[chain_j] > 0) ? make_sec(seqy, ya, ylen, secy, atom_opt) : make_sec(ya, ylen, secy);

                    if (byresi_opt)
                        extract_aln_from_resi(sequence, seqx, seqy, resi_vec1, resi_vec2, byresi_opt);

                    // --- CORE DISPATCH LOGIC START ---
                    if (mode == FLEX_USBCAT)
                    {
                        FlexAlignResult usbcat_res;
                        bool force_fast_opt = (getmin(xlen, ylen) > 1500) ? true : fast_opt;

                        usbcat_res.hingeNum = flexalign_usbcat_main(
                            xa, ya, seqx, seqy, secx, secy,
                            usbcat_res.t0, usbcat_res.u0, usbcat_res.tu_vec,
                            usbcat_res.TM1, usbcat_res.TM2, usbcat_res.TM3, usbcat_res.TM4, usbcat_res.TM5,
                            usbcat_res.d0_0, usbcat_res.TM_0,
                            usbcat_res.d0A, usbcat_res.d0B, usbcat_res.d0u, usbcat_res.d0a, usbcat_res.d0_out,
                            usbcat_res.seqM, usbcat_res.seqxA, usbcat_res.seqyA, usbcat_res.do_vec,
                            usbcat_res.rmsd0, usbcat_res.L_ali, usbcat_res.Liden,
                            usbcat_res.TM_ali, usbcat_res.rmsd_ali, usbcat_res.n_ali, usbcat_res.n_ali8,
                            xlen, ylen, sequence, Lnorm_ass, d0_scale,
                            i_opt, a_opt, u_opt, d_opt, force_fast_opt,
                            mol_vec1[chain_i] + mol_vec2[chain_j], hinge_opt, ss_opt, 0, hinge_set, TMpass);

                        if (outfmt_opt == 0)
                            print_version();
                        output_flexalign_results(
                            xname.substr(dir1_opt.size() + dir_opt.size() + dirpair_opt.size()),
                            yname.substr(dir2_opt.size() + dir_opt.size() + dirpair_opt.size()),
                            chainID_list1[chain_i], chainID_list2[chain_j],
                            xlen, ylen, usbcat_res.t0, usbcat_res.u0, usbcat_res.tu_vec,
                            usbcat_res.TM1, usbcat_res.TM2, usbcat_res.TM3, usbcat_res.TM4, usbcat_res.TM5,
                            usbcat_res.rmsd0, usbcat_res.d0_out, usbcat_res.seqM.c_str(),
                            usbcat_res.seqxA.c_str(), usbcat_res.seqyA.c_str(), usbcat_res.Liden,
                            usbcat_res.n_ali8, usbcat_res.L_ali, usbcat_res.TM_ali, usbcat_res.rmsd_ali,
                            usbcat_res.TM_0, usbcat_res.d0_0,
                            usbcat_res.d0A, usbcat_res.d0B, Lnorm_ass, d0_scale, usbcat_res.d0a, usbcat_res.d0u,
                            (m_opt ? fname_matrix : "").c_str(),
                            outfmt_opt, ter_opt, false, split_opt, o_opt,
                            fname_super, i_opt, a_opt, u_opt, d_opt, mirror_opt,
                            resi_vec1, resi_vec2);
                    }
                    else
                    {
                        // === Standard & Best specific logic ===
                        FlexAlignResult best_res;
                        double global_max_TM = -1.0;

                        int start_ss = (mode == FLEX_BEST) ? 0 : ss_opt;
                        int end_ss = (mode == FLEX_BEST) ? 1 : ss_opt;

                        bool force_fast_opt = (getmin(xlen, ylen) > 1500) ? true : fast_opt;

                        for (int cur_ss_opt = start_ss; cur_ss_opt <= end_ss; cur_ss_opt++)
                        {
                            FlexAlignResult cur_res;
                            execute_flexalign_with_fallback(
                                xa, ya, seqx, seqy, secx, secy, xlen, ylen, sequence, Lnorm_ass, d0_scale,
                                i_opt, a_opt, u_opt, d_opt, force_fast_opt, mol_vec1[chain_i] + mol_vec2[chain_j],
                                hinge_opt, cur_ss_opt, cur_res);

                            double cur_max_TM = (cur_res.TM1 > cur_res.TM2) ? cur_res.TM1 : cur_res.TM2;
                            if (cur_max_TM > global_max_TM)
                            {
                                global_max_TM = cur_max_TM;
                                best_res = cur_res;
                            }
                        }

                        if (outfmt_opt == 0)
                            print_version();
                        output_flexalign_results(
                            xname.substr(dir1_opt.size() + dir_opt.size() + dirpair_opt.size()),
                            yname.substr(dir2_opt.size() + dir_opt.size() + dirpair_opt.size()),
                            chainID_list1[chain_i], chainID_list2[chain_j],
                            xlen, ylen, best_res.t0, best_res.u0, best_res.tu_vec, best_res.TM1, best_res.TM2, best_res.TM3, best_res.TM4, best_res.TM5,
                            best_res.rmsd0, best_res.d0_out, best_res.seqM.c_str(),
                            best_res.seqxA.c_str(), best_res.seqyA.c_str(), best_res.Liden,
                            best_res.n_ali8, best_res.L_ali, best_res.TM_ali, best_res.rmsd_ali, best_res.TM_0, best_res.d0_0,
                            best_res.d0A, best_res.d0B, Lnorm_ass, d0_scale, best_res.d0a, best_res.d0u,
                            (m_opt ? fname_matrix : "").c_str(),
                            outfmt_opt, ter_opt, false, split_opt, o_opt,
                            fname_super, i_opt, a_opt, u_opt, d_opt, mirror_opt,
                            resi_vec1, resi_vec2);
                    }
                    // --- CORE DISPATCH LOGIC END ---

                    // Cleanup memory
                    DeleteArray(&ya, ylen);
                    delete[] seqy;
                    delete[] secy;
                    resi_vec2.clear();
                }
                if (chain2_list.size() > 1)
                {
                    yname.clear();
                    for (chain_j = 0; chain_j < ychainnum; chain_j++)
                        PDB_lines2[chain_j].clear();
                    PDB_lines2.clear();
                    chainID_list2.clear();
                    mol_vec2.clear();
                }
            }
            PDB_lines1[chain_i].clear();
            DeleteArray(&xa, xlen);
            delete[] seqx;
            delete[] secx;
            resi_vec1.clear();
        }
        xname.clear();
        PDB_lines1.clear();
        chainID_list1.clear();
        mol_vec1.clear();
    }
    if (chain2_list.size() == 1)
    {
        yname.clear();
        for (chain_j = 0; chain_j < ychainnum; chain_j++)
            PDB_lines2[chain_j].clear();
        PDB_lines2.clear();
        resi_vec2.clear();
        chainID_list2.clear();
        mol_vec2.clear();
    }
    return 0;
}

// =======================================================================
// Direct Drop-in Wrappers (No changes needed in main() bindings)
// =======================================================================

int flexalign_greedy(string &xname, string &yname, const string &fname_super, const string &fname_lign, const string &fname_matrix, vector<string> &sequence, const double Lnorm_ass, const double d0_scale, const bool m_opt, const int i_opt, const int o_opt, const int a_opt, const bool u_opt, const bool d_opt, const double TMcut, const int infmt1_opt, const int infmt2_opt, const int ter_opt, const int split_opt, const int outfmt_opt, const bool fast_opt, const int mirror_opt, const int het_opt, const string &atom_opt, const bool autojustify, const string &mol_opt, const string &dir_opt, const string &dirpair_opt, const string &dir1_opt, const string &dir2_opt, const vector<string> &chain2parse1, const vector<string> &chain2parse2, const vector<string> &model2parse1, const vector<string> &model2parse2, const int byresi_opt, const vector<string> &chain1_list, const vector<string> &chain2_list, const int hinge_opt)
{
    return flexalign_unified(xname, yname, fname_super, fname_lign, fname_matrix, sequence, Lnorm_ass, d0_scale, m_opt, i_opt, o_opt, a_opt, u_opt, d_opt, TMcut, infmt1_opt, infmt2_opt, ter_opt, split_opt, outfmt_opt, fast_opt, mirror_opt, het_opt, atom_opt, autojustify, mol_opt, dir_opt, dirpair_opt, dir1_opt, dir2_opt, chain2parse1, chain2parse2, model2parse1, model2parse2, byresi_opt, chain1_list, chain2_list, hinge_opt, 0 /* ss_opt is ignored in BEST mode */, FLEX_BEST);
}

int flexalign_usbcat(string &xname, string &yname, const string &fname_super, const string &fname_lign, const string &fname_matrix, vector<string> &sequence, const double Lnorm_ass, const double d0_scale, const bool m_opt, const int i_opt, const int o_opt, const int a_opt, const bool u_opt, const bool d_opt, const double TMcut, const int infmt1_opt, const int infmt2_opt, const int ter_opt, const int split_opt, const int outfmt_opt, const bool fast_opt, const int mirror_opt, const int het_opt, const string &atom_opt, const bool autojustify, const string &mol_opt, const string &dir_opt, const string &dirpair_opt, const string &dir1_opt, const string &dir2_opt, const vector<string> &chain2parse1, const vector<string> &chain2parse2, const vector<string> &model2parse1, const vector<string> &model2parse2, const int byresi_opt, const vector<string> &chain1_list, const vector<string> &chain2_list, const int hinge_opt, bool hinge_set = false, double TMpass = 0.85)
{
    return flexalign_unified(xname, yname, fname_super, fname_lign, fname_matrix, sequence, Lnorm_ass, d0_scale, m_opt, i_opt, o_opt, a_opt, u_opt, d_opt, TMcut, infmt1_opt, infmt2_opt, ter_opt, split_opt, outfmt_opt, fast_opt, mirror_opt, het_opt, atom_opt, autojustify, mol_opt, dir_opt, dirpair_opt, dir1_opt, dir2_opt, chain2parse1, chain2parse2, model2parse1, model2parse2, byresi_opt, chain1_list, chain2_list, hinge_opt, 0 /* ss_opt ignore */, FLEX_USBCAT, hinge_set, TMpass);
}

int main(int argc, char *argv[])
{
    if (argc < 2)
        print_help();

    clock_t t1, t2;
    t1 = clock();

    /**********************/
    /*    get argument    */
    /**********************/
    string xname = "";
    string yname = "";
    string fname_super = "";  // file name for superposed structure
    string fname_lign = "";   // file name for user alignment
    string fname_matrix = ""; // file name for output matrix
    vector<string> sequence;  // get value from alignment file
    double Lnorm_ass, d0_scale;

    bool h_opt = false;  // print full help message
    bool v_opt = false;  // print version
    bool m_opt = false;  // flag for -m, output rotation matrix
    int i_opt = 0;       // 1 for -i, 3 for -I
    int o_opt = 0;       // 1 for -o, 2 for -rasmol, 3 for -chimerax
    int a_opt = 0;       // flag for -a, do not normalized by average length
    bool u_opt = false;  // flag for -u, normalized by user specified length
    bool d_opt = false;  // flag for -d, user specified d0

    bool full_opt = false; // do not show chain level alignment
    double TMcut = -1;
    int infmt1_opt = -1;   // PDB or PDBx/mmCIF format for chain_1
    int infmt2_opt = -1;   // PDB or PDBx/mmCIF format for chain_2
    int ter_opt = -1;      // default change to 2 (END, or different chainID)
    int split_opt = -1;    // default change to 2 (split each chains)
    int outfmt_opt = 0;    // set -outfmt to full output
    bool fast_opt = false; // flags for -fast, fTM-align algorithm
    int hinge_opt = 9;     // maximum number of hinge allowed for flexible
    bool hinge_set = false;
    double TMpass_opt = 0.85;
    int mirror_opt = 0;         // do not align mirror
    int het_opt = 0;            // do not read HETATM residues
    int mm_opt = 0;             // do not perform MM-align
    bool usbcat_opt = false;    // flag for -afp, only valid with -mm 7
    string atom_opt = "auto";   // use C alpha atom for protein and C3' for RNA
    string mol_opt = "auto";    // auto-detect the molecule type as protein/RNA
    string suffix_opt = "";     // set -suffix to empty
    string dir_opt = "";        // set -dir to empty
    string dirpair_opt = "";    // set -dirpair to empty
    string dir1_opt = "";       // set -dir1 to empty
    string dir2_opt = "";       // set -dir2 to empty
    int byresi_opt = 0;         // set -byresi to 0
    vector<string> chain1_list; // only when -dir1 is set
    vector<string> chain2_list; // only when -dir2 is set
    vector<string> chain2parse1;
    vector<string> chain2parse2;
    vector<string> model2parse1;
    vector<string> model2parse2;
    vector<pair<string, string> > chain_pair_list; // only when -dirpair is set

    for (int i = 1; i < argc; i++)
    {
        if (!strcmp(argv[i], "-o"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -o");
            if (o_opt == 2)
                cerr << "Warning! -rasmol is already set. Ignore -o" << endl;
            else if (o_opt == 3)
                cerr << "Warning! -chimerax is already set. Ignore -o" << endl;
            else
            {
                fname_super = argv[i + 1];
                o_opt = 1;
            }
            i++;
        }
        else if (!strcmp(argv[i], "-rasmol"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -rasmol");
            if (o_opt == 1)
                cerr << "Warning! -o is already set. Ignore -rasmol" << endl;
            else if (o_opt == 3)
                cerr << "Warning! -chimerax is already set. Ignore -rasmol" << endl;
            else
            {
                fname_super = argv[i + 1];
                o_opt = 2;
            }
            i++;
        }
        else if (!strcmp(argv[i], "-chimerax"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -chimerax");
            if (o_opt == 1)
                cerr << "Warning! -o is already set. Ignore -chimerax" << endl;
            else if (o_opt == 2)
                cerr << "Warning! -rasmol is already set. Ignore -chimerax" << endl;
            else
            {
                fname_super = argv[i + 1];
                o_opt = 3;
            }
            i++;
        }
        else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "-L"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -u or -L");
            Lnorm_ass = atof(argv[i + 1]);
            u_opt = true;
            i++;
            if (Lnorm_ass <= 0)
                PrintErrorAndQuit(
                    "ERROR! The value for -u or -L should be >0");
        }
        else if (!strcmp(argv[i], "-a"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -a");
            if (!strcmp(argv[i + 1], "T"))
                a_opt = true;
            else if (!strcmp(argv[i + 1], "F"))
                a_opt = false;
            else
            {
                a_opt = atoi(argv[i + 1]);
                if (a_opt != -2 && a_opt != -1 && a_opt != 1)
                    PrintErrorAndQuit("-a must be -2, -1, 1, T or F");
            }
            i++;
        }
        else if (!strcmp(argv[i], "-d"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -d");
            d0_scale = atof(argv[i + 1]);
            d_opt = true;
            i++;
        }
        else if (!strcmp(argv[i], "-hinge"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -hinge");
            hinge_set = true;
            hinge_opt = atoi(argv[i + 1]);
            i++;
        }
        else if (!strcmp(argv[i], "-v"))
        {
            v_opt = true;
        }
        else if (!strcmp(argv[i], "-h"))
        {
            h_opt = true;
        }
        else if (!strcmp(argv[i], "-chain1"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -chain1");
            split(argv[i + 1], chain2parse1, ',');
            i++;
        }
        else if (!strcmp(argv[i], "-chain2"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -chain2");
            split(argv[i + 1], chain2parse2, ',');
            i++;
        }
        else if (!strcmp(argv[i], "-model1"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -model1");
            split(argv[i + 1], model2parse1, ',');
            i++;
        }
        else if (!strcmp(argv[i], "-model2"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -model2");
            split(argv[i + 1], model2parse2, ',');
            i++;
        }
        else if (!strcmp(argv[i], "-m"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -m");
            fname_matrix = argv[i + 1];
            m_opt = true;
            i++;
        } // get filename for rotation matrix
        else if (!strcmp(argv[i], "-fast"))
        {
            fast_opt = true;
        }
        else if (!strcmp(argv[i], "-infmt1"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -infmt1");
            infmt1_opt = atoi(argv[i + 1]);
            i++;
            if (infmt1_opt < -1 || infmt1_opt > 3)
                PrintErrorAndQuit("ERROR! -infmt1 can only be -1, 0, 1, 2, or 3");
        }
        else if (!strcmp(argv[i], "-infmt2"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -infmt2");
            infmt2_opt = atoi(argv[i + 1]);
            i++;
            if (infmt2_opt < -1 || infmt2_opt > 3)
                PrintErrorAndQuit("ERROR! -infmt2 can only be -1, 0, 1, 2, or 3");
        }
        else if (!strcmp(argv[i], "-ter"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -ter");
            ter_opt = atoi(argv[i + 1]);
            i++;
        }
        else if (!strcmp(argv[i], "-split"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -split");
            split_opt = atoi(argv[i + 1]);
            i++;
        }
        else if (!strcmp(argv[i], "-atom"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -atom");
            atom_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-mol"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -mol");
            mol_opt = argv[i + 1];
            i++;
            if (mol_opt == "prot")
                mol_opt = "protein";
            else if (mol_opt == "DNA")
                mol_opt = "RNA";
            if (mol_opt != "auto" && mol_opt != "protein" && mol_opt != "RNA")
                PrintErrorAndQuit("ERROR! Molecule type must be one of the "
                                  "following:\nauto, prot (the same as 'protein'), and "
                                  "RNA (the same as 'DNA').");
        }
        else if (!strcmp(argv[i], "-dir"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -dir");
            dir_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-dirpair"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -dirpair");
            dirpair_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-dir1"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -dir1");
            dir1_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-dir2"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -dir2");
            dir2_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-suffix"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -suffix");
            suffix_opt = argv[i + 1];
            i++;
        }
        else if (!strcmp(argv[i], "-outfmt"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -outfmt");
            outfmt_opt = atoi(argv[i + 1]);
            i++;
        }
        else if (!strcmp(argv[i], "-mirror"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -mirror");
            mirror_opt = atoi(argv[i + 1]);
            i++;
        }
        else if (!strcmp(argv[i], "-het"))
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -het");
            het_opt = atoi(argv[i + 1]);
            i++;
            if (het_opt != 0 && het_opt != 1 && het_opt != 2)
                PrintErrorAndQuit("-het must be 0, 1, or 2");
        }
        else if (!strcmp(argv[i], "-afp"))
        {
            usbcat_opt = true;
        }
        else if (!strcmp(argv[i], "-TMpass")) // Parse the -TMpass argument
        {
            if (i >= (argc - 1))
                PrintErrorAndQuit("ERROR! Missing value for -TMpass");
            TMpass_opt = atof(argv[i + 1]);
            i++;
        }
        else if (xname.size() == 0)
            xname = argv[i];
        else if (yname.size() == 0)
            yname = argv[i];
        else
            PrintErrorAndQuit(string("ERROR! Undefined option ") + argv[i]);
    }

    if (xname.size() == 0 || (yname.size() && dir_opt.size()) ||
        (yname.size() && dirpair_opt.size()) ||
        (yname.size() == 0 && dir_opt.size() == 0 && dirpair_opt.size() == 0))
    {
        if (h_opt)
            print_help(h_opt);
        if (v_opt)
        {
            print_version();
            exit(EXIT_FAILURE);
        }
        if (xname.size() == 0)
            PrintErrorAndQuit("Please provide input structures");
        else if (yname.size() == 0 && dir_opt.size() == 0 && dirpair_opt.size() == 0 && mm_opt != 4)
            PrintErrorAndQuit("Please provide structure B");
        else if (yname.size() && dir_opt.size() + dirpair_opt.size())
            PrintErrorAndQuit("Please provide only one file name if -dir is set");
    }

    if (suffix_opt.size() && dir_opt.size() + dirpair_opt.size() + dir1_opt.size() + dir2_opt.size() == 0)
        PrintErrorAndQuit("-suffix is only valid if -dir, -dir1 or -dir2 is set");
    if ((dir_opt.size() || dirpair_opt.size() || dir1_opt.size() || dir2_opt.size()))
    {
        if (mm_opt != 2 && mm_opt != 4)
        {
            if (o_opt)
                PrintErrorAndQuit("-o cannot be set with -dir, -dir1 or -dir2");
            if (m_opt && fname_matrix != "-")
                PrintErrorAndQuit("-m can only be - or unset when using -dir, -dir1 or -dir2");
        }
        else if ((dir_opt.size() || dirpair_opt.size()) && (dir1_opt.size() || dir2_opt.size()))
            PrintErrorAndQuit("-dir cannot be set with -dir1 or -dir2");
        else if (dir_opt.size() && dirpair_opt.size())
            PrintErrorAndQuit("-dir cannot be set with -dirpair");
    }
    if (o_opt && (infmt1_opt != -1 && infmt1_opt != 0 && infmt1_opt != 3))
        PrintErrorAndQuit("-o can only be used with -infmt1 -1, 0 or 3");

    bool autojustify = (atom_opt == "auto" || atom_opt == "PC4'"); // auto re-pad atom name
    if (mol_opt == "protein" && atom_opt == "auto")
        atom_opt = " CA ";
    else if (mol_opt == "RNA" && atom_opt == "auto")
        atom_opt = " C3'";
    if (atom_opt.size() != 4)
    {
        cerr << "ERROR! Atom name must have 4 characters, including space.\n"
                "For example, C alpha, C3' and P atoms should be specified by\n"
                "-atom \" CA \", -atom \" P  \" and -atom \" C3'\", respectively."
             << endl;
        if (atom_opt.size() >= 5 || atom_opt.size() == 0)
            return 1;
        else if (atom_opt.size() == 1)
            atom_opt = " " + atom_opt + "  ";
        else if (atom_opt.size() == 2)
            atom_opt = " " + atom_opt + " ";
        else if (atom_opt.size() == 3)
            atom_opt = " " + atom_opt;
        cerr << "Change -atom to \"" << atom_opt << "\"" << endl;
    }

    if (d_opt && d0_scale <= 0)
        PrintErrorAndQuit("Wrong value for option -d! It should be >0");
    if (outfmt_opt >= 2 && (a_opt || u_opt || d_opt))
        PrintErrorAndQuit("-outfmt 2 cannot be used with -a, -u, -L, -d");
    if (split_opt < 0)
            split_opt = 2;
    else if (split_opt > 2)
        PrintErrorAndQuit("-split can only be 0, 1 or 2");

    if (mirror_opt && het_opt != 1)
        cerr << "WARNING! -mirror was not used with -het 1. "
             << "D amino acids may not be correctly aligned." << endl;

    if (ter_opt < 0)
            ter_opt = 2;

    if (o_opt==2 && ter_opt <= 1 && split_opt == 2)
    {
            cerr << "WARNING! -mm may generate incorrect"
                 << " RasMol output due to limitations in PDB file format. "
                 << "When -mm is used, -o is recommended over -rasmol" << endl;
    }

    if (hinge_opt >= 10)
        PrintErrorAndQuit("ERROR! -hinge must be <10");

    /* read initial alignment file from 'align.txt' */
    if (i_opt)
        read_user_alignment(sequence, fname_lign, i_opt);

    if (m_opt && fname_matrix == "") // Output rotation matrix: matrix.txt
        PrintErrorAndQuit("ERROR! Please provide a file name for option -m!");

    /* parse file list */
    int i;
    if (dirpair_opt.size())
        file2chainpairlist(chain1_list, chain2_list, xname, dirpair_opt, suffix_opt);
    else
    {
        if (dir1_opt.size() + dir_opt.size() == 0)
            chain1_list.push_back(xname);
        else
            file2chainlist(chain1_list, xname, dir_opt + dir1_opt, suffix_opt);

        if (dir_opt.size())
            for (i = 0; i < chain1_list.size(); i++)
                chain2_list.push_back(chain1_list[i]);
        else if (dir2_opt.size() == 0)
            chain2_list.push_back(yname);
        else
            file2chainlist(chain2_list, yname, dir2_opt, suffix_opt);
    }

    if (outfmt_opt == 2)
    {
            cout << "#PDBchain1\tPDBchain2\tTM1\tTM2\t"
                 << "RMSD\tID1\tID2\tIDali\tL1\tL2\tLali\tNblk" << endl;
    }

    /* real alignment. entry functions are MMalign_main and
     * TMalign_main */
    {
        if (usbcat_opt)
            flexalign_usbcat(xname, yname, fname_super, fname_lign,
                             fname_matrix, sequence, Lnorm_ass, d0_scale, m_opt, i_opt, o_opt,
                             a_opt, u_opt, d_opt, TMcut, infmt1_opt, infmt2_opt, ter_opt,
                             split_opt, outfmt_opt, fast_opt, mirror_opt, het_opt,
                             atom_opt, autojustify, mol_opt, dir_opt, dirpair_opt, dir1_opt,
                             dir2_opt, chain2parse1, chain2parse2, model2parse1, model2parse2,
                             byresi_opt, chain1_list, chain2_list, hinge_opt, hinge_set, TMpass_opt);
        else
            flexalign_greedy(xname, yname, fname_super, fname_lign,
                             fname_matrix, sequence, Lnorm_ass, d0_scale, m_opt, i_opt, o_opt,
                             a_opt, u_opt, d_opt, TMcut, infmt1_opt, infmt2_opt, ter_opt,
                             split_opt, outfmt_opt, fast_opt, mirror_opt, het_opt,
                             atom_opt, autojustify, mol_opt, dir_opt, dirpair_opt, dir1_opt,
                             dir2_opt, chain2parse1, chain2parse2, model2parse1, model2parse2,
                             byresi_opt, chain1_list, chain2_list, hinge_opt);
    }

    /* clean up */
    vector<string>().swap(chain1_list);
    vector<string>().swap(chain2_list);
    vector<string>().swap(chain2parse1);
    vector<string>().swap(chain2parse2);
    vector<string>().swap(model2parse1);
    vector<string>().swap(model2parse2);
    vector<string>().swap(sequence);
    vector<pair<string, string> >().swap(chain_pair_list);

    t2 = clock();
    float diff = ((float)t2 - (float)t1) / CLOCKS_PER_SEC;
    if (outfmt_opt < 2)
        printf("#Total CPU time is %5.2f seconds\n", diff);
    return 0;
}

// ===== END USFlexalign.cpp =====
