/// <summary>
        ///   Writes this binary table with data first going
        ///   to a temp file (and heap file if necessary),
        ///   then with header going to destination stream,
        ///   then copying data from temp file to destination stream,
        ///   then if necessary copying heap file to destination stream.
        /// </summary>
        /// <param name="s">The destination stream.</param>
        /// steps:
        /// 1) write the table to a tempfile
        ///    byterenderers write data to the heap if necessary
        ///    byterenderers return heap positions and lengths if necessary
        ///    these are returned as a byte sequence like any other data
        ///    and are written to the table like any other data
        /// 2) fix the header
        ///    write the header to the main stream
        /// 3) write the table tempfile to the main stream
        /// 4) write the heap tempfile to the main stream
        /// what a pain
        protected void WriteHeapOutputWithTempTableAndHeapFiles(ArrayDataIO s)
        {
            String tempFilename = CreateTempFilename() + "temp.tmp";
              String heapFilename = CreateTempFilename() + "heap.tmp";
              Stream tempS = new ActualBufferedStream(new FileStream(tempFilename, FileMode.Create));
              HeapStream heapS = null;
              int[] maxColWidths = null;
              bool _doHeap = _hasStrings && _writeMode != StringWriteMode.TRUNCATE;

              if(_doHeap)
              {
            maxColWidths = new int[_byteRenderers.Length];
            heapS = new HeapStream(new FileStream(heapFilename, FileMode.Create));
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Heap = heapS;
              maxColWidths[col] = -1;
            }
              }

              #region 1) write the table
              int nRows = 0;
              for(Array[] els = _rs.GetNextRow(ref _row); els != null;)
              {
            ++nRows;
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Write(els[col], tempS);
              if(_doHeap && els[col] is String[])
              {
                maxColWidths[col] = maxColWidths[col] < ((String[])els[col])[0].Length ?
                  ((String[])els[col])[0].Length : maxColWidths[col];
              }
            }

            els = _rs.GetNextRow(ref _row);
              }
              tempS.Flush();
              #endregion

              #region 2) fix the header and write it to the main stream
              myHeader.RemoveCard("NAXIS2");
              myHeader.SetNaxis(2, nRows);
              // shoehorn correct heap information into header
              // PCOUNT, THEAP, and TFORMn
              // fix NAXIS1
              if(_doHeap)
              {
            heapS.Flush();
            int theap = (nRows * _rowSizeInBytes);
            int pad = FitsUtil.Padding((long)theap + heapS.Position);
            int pcount = (int)heapS.Position + pad;
            // here we correct for swapping out actual strings with heap indices/lengths
            myHeader.RemoveCard("NAXIS1");
            myHeader.InsertCard(new HeaderCard("NAXIS1", _rowSizeInBytes, null), "NAXIS2");
            myHeader.RemoveCard("PCOUNT");
            myHeader.InsertCard(new HeaderCard("PCOUNT", pcount, "Length of heap area in bytes"),
              "GCOUNT");
            myHeader.RemoveCard("THEAP");
            myHeader.AddValue("THEAP", theap, "Position of heap wrt start of binary table");
              }

              // fix the TFORMn entries for string columns
              IEnumerator ie = null;
              bool found = false;
              //for(int i = 0; i < maxColWidths.Length; ++i, found = false)
              for(int i = 0; i < _rs.ModelRow.Length; ++i, found = false)
              {
            if(_rs.ModelRow[i] is String[])
            {
              ie = myHeader.GetEnumerator();
              ie.MoveNext();
              for(int j = 0; !found && ie.Current != null; ++j, ie.MoveNext())
              {
                if(("TFORM" + (i + 1)).Equals(((DictionaryEntry)ie.Current).Key))
                {
                  if(_doHeap)
                  {
                    myHeader.RemoveCard(j);
                    myHeader.
                      InsertCard(new HeaderCard("TFORM" + (i + 1),
                      "1PA(" + maxColWidths[i] + ")",
                      null), j);
                    found = true;
                  }
                  else
                  {
                    myHeader.RemoveCard(j);
                    myHeader.
                      InsertCard(new HeaderCard("TFORM" + (i + 1),
                      _stringTruncationLength + "A",
                      null), j);
                    found = true;
                  }
                }
              }
            }
              }
              myHeader.Write(s);
              #endregion

              #region 3) write the table tempfile to the main stream
              tempS.Seek(0, SeekOrigin.Begin);
              for(int nRead = tempS.Read(_buf, 0, _buf.Length); nRead > 0;)
              {
            s.Write(_buf, 0, nRead);
            nRead = tempS.Read(_buf, 0, _buf.Length);
              }
              tempS.Close();
              File.Delete(tempFilename);

              // if there's a heap, pad the heap instead of the table
              if(!_doHeap)
              {
            int pad = FitsUtil.Padding((long)nRows * (long)_rowSizeInBytes);
            s.Write(new byte[pad], 0, pad);
              }
              #endregion

              #region 4) write the heap tempfile to the main stream
              if(_doHeap)
              {
            // calculate the pad
            int pad = FitsUtil.Padding((long)nRows * (long)_rowSizeInBytes + heapS.Position);

            // write to the main stream
            heapS.Seek(0, SeekOrigin.Begin);
            for(int nRead = heapS.Read(_buf, 0, _buf.Length); nRead > 0;)
            {
              s.Write(_buf, 0, nRead);
              nRead = heapS.Read(_buf, 0, _buf.Length);
            }
            heapS.Close();
            File.Delete(heapFilename);

            // pad the file
            s.Write(new byte[pad], 0, pad);
              }
              #endregion
        }
        /// <summary>
        ///   Writes this binary table with data first going
        ///   to a temp file (and heap file if necessary),
        ///   then with header going to destination stream,
        ///   then merging heap with table data if necessary
        ///   and copying these to destination stream.
        /// </summary>
        /// <param name="s">The destination stream.</param>
        /// steps:
        /// 1) write the table to a tempfile
        ///    byterenderers write data to the heap if necessary
        ///    byterenderers return heap positions and lengths if necessary
        ///    these are returned as a byte sequence like any other data
        ///    and are written to the table like any other data
        /// 2) fix the header
        ///    write the header to the main stream
        /// 3) write the table tempfile to the main stream, merging heap if necessary
        /// what a pain
        protected void WritePadOutput(ArrayDataIO s)
        {
            String tempFilename = CreateTempFilename() + "temp.tmp";
              String heapFilename = CreateTempFilename() + "heap.tmp";
              Stream tempS = new ActualBufferedStream(new FileStream(tempFilename, FileMode.Create));
              Stream heapS = null;
              //Stream tempS = new BufferedStream(new FileStream(tempFilename, FileMode.Create), 4096);
              int[] maxColWidths = null;
              int[] stringIndices = GetStringIndices(_rs.ModelRow);
              int[] byteWidths = ComputeByteWidths(CopyModelRowStripUnknowns(ReplaceTroolean(_rs.ModelRow), new byte[1]));
              int nRows = 0;
              int maxColWidth = 0;

              if(_hasStrings)
              {
            maxColWidths = new int[_byteRenderers.Length];
            heapS = new HeapStream(new FileStream(heapFilename, FileMode.Create));
            //heapS = new BufferedStream(new FileStream(heapFilename, FileMode.Create));
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Heap = heapS;
              maxColWidths[col] = -1;
            }
              }

              #region 1) write the table
              for(Array[] els = _rs.GetNextRow(ref _row); els != null;)
              {
            ++nRows;
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Write(els[col], tempS);
              if(els[col] is String[] && maxColWidths[col] < ((String[])els[col])[0].Length)
              {
                maxColWidths[col] = ((String[])els[col])[0].Length;

                if(maxColWidth < maxColWidths[col])
                {
                  maxColWidth = maxColWidths[col];
                }
              }
            }

            els = _rs.GetNextRow(ref _row);
              }
              tempS.Flush();
              heapS.Flush();
              #endregion

              #region 2) fix the header and write it to the main stream
              if(_hasStrings)
              {
            // fix NAXIS1, NAXIS2
            Array[] modelRow2 = CopyModelRowReplaceStrings(ReplaceTroolean(_rs.ModelRow), null);
            //modelRow2 = CopyModelRowStripUnknowns(modelRow2, new byte[1]);
            for(int i = 0; i < modelRow2.Length; ++i)
            {
              if(modelRow2[i] == null)
              {
                modelRow2[i] = new String[]{new String(' ', maxColWidths[i])};
                myHeader.RemoveCard("TFORM" + (i + 1));
                myHeader.InsertValue("TFORM" + (i + 1), maxColWidths[i] + "A", null, "TDIM" + (i + 1));
              }
            }
            modelRow2 = CopyModelRowStripUnknowns(modelRow2, new byte[1]);
            myHeader.RemoveCard("NAXIS1");
            myHeader.InsertValue("NAXIS1", ArrayFuncs.ComputeSize(modelRow2), "row width in bytes", "NAXIS2");
            myHeader.RemoveCard("NAXIS2");
            myHeader.InsertValue("NAXIS2", nRows, "number of rows", "PCOUNT");
            myHeader.RemoveCard("THEAP");
              }
              myHeader.Write(s);
              #endregion

              #region 3) write the table tempfile to the main stream
              tempS.Seek(0, SeekOrigin.Begin);
              heapS.Seek(0, SeekOrigin.Begin);
              // man, if you can't even fit a row into memory, I give up
              byte[] row = new byte[_rowSizeInBytes]; // this is the old size
              byte[] padBuf = SupportClass.ToByteArray(new String(_padChar, maxColWidth));
              int len = 0;
              int off = 0;
              for(int nRead = tempS.Read(row, 0, row.Length), rowOffset = 0; nRead > 0; rowOffset = 0)
              {
            for(int i = 0; i < byteWidths.Length; ++i)
            {
              if(stringIndices[i] != -1)
              {
                Array.Reverse(row, stringIndices[i], 4); // fix the length bytes
                Array.Reverse(row, stringIndices[i] + 4, 4); // fix the pos bytes
                len = BitConverter.ToInt32(row, stringIndices[i]);
                off = BitConverter.ToInt32(row, stringIndices[i] + 4);
                if(_padLeft)
                {
                  s.Write(padBuf, 0, maxColWidths[i] - len);
                  heapS.Seek(off, SeekOrigin.Begin);
                  int bufread = heapS.Read(_buf, 0, len);
                  s.Write(_buf, 0, len);
                }
                else
                {
                  heapS.Seek(off, SeekOrigin.Begin);
                  heapS.Read(_buf, 0, len);
                  s.Write(_buf, 0, len);
                  s.Write(padBuf, 0, maxColWidths[i] - len);
                }
                rowOffset += 8; // advance 2 ints into the row
              }
              else
              {
                // s better be buffered, or this is going to be slow.  But since s is ArrayDataIO,
                // and the only current concrete ArrayDataIO implementations are buffered,
                // I think we're good.
                // **** MAKE SURE BUFFEREDSTREAM USED BY BUFFEREDDATASTREAM IS GOOD *****
                s.Write(row, rowOffset, byteWidths[i]);
                rowOffset += byteWidths[i];
              }
            }
            nRead = tempS.Read(row, 0, row.Length);
              }
              tempS.Close();
              heapS.Close();
              File.Delete(tempFilename);
              File.Delete(heapFilename);

              // pad the table
              int tableWidth = 0;
              for(int i = 0; i < byteWidths.Length; ++i)
              {
            if(stringIndices[i] != -1)
            {
              tableWidth += maxColWidths[i];
            }
            else
            {
              tableWidth += byteWidths[i];
            }
              }
              int pad = FitsUtil.Padding((long)nRows * (long)tableWidth);
              s.Write(new byte[pad], 0, pad);
              #endregion
        }
        /// <summary>
        ///   Writes this binary table with heap temp file if necessary,
        ///   then fixing nRows and PCOUNT in header,
        ///   then if necessary copying heap file to destination stream.
        /// </summary>
        /// <param name="s">The destination stream.</param>
        /// steps:
        /// 1) write the header to the main stream
        ///    write the table to the main stream
        ///    byterenderers write data to the heap if necessary
        ///    byterenderers return heap positions and lengths if necessary
        ///    these are returned as a byte sequence like any other data
        ///    and are written to the table like any other data
        /// 2) fix the header
        /// 3) write the heap tempfile to the main stream
        /// what a pain
        protected void WriteHeapOutputWithTempHeapFile(ArrayDataIO s)
        {
            String heapFilename = CreateTempFilename() + "heap.tmp";
              HeapStream heapS = null;
              int[] maxColWidths = null;

              if(_hasStrings)
              {
            maxColWidths = new int[_byteRenderers.Length];
            heapS = new HeapStream(new FileStream(heapFilename, FileMode.Create));
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Heap = heapS;
              maxColWidths[col] = -1;
            }
              }

              #region 1) write the header and the table
              // prep header to make sure it will line up properly with the table later on.
              // since we made the header, we know that anything we add later
              // (except THEAP, accounted for here) will already have been there,
              // so we're not inflating the header
              myHeader.RemoveCard("THEAP");
              if(_hasStrings && _writeMode == StringWriteMode.HEAP)
              {
            myHeader.AddValue("THEAP", 0, null);
              }
              long headerMark = s.Position;
              myHeader.Write(s);

              int nRows = 0;
              for(Array[] els = _rs.GetNextRow(ref _row); els != null;)
              {
            ++nRows;
            for(int col = 0; col < _byteRenderers.Length; ++col)
            {
              _byteRenderers[col].Write(els[col], s);
              if(els[col] is String[])
              {
                maxColWidths[col] = maxColWidths[col] < ((String[])els[col])[0].Length ?
                  ((String[])els[col])[0].Length : maxColWidths[col];
              }
            }

            els = _rs.GetNextRow(ref _row);
              }

              // pad the table.  if there's a heap, pad the heap instead
              if(!_hasStrings)
              {
            int pad = FitsUtil.Padding((long)nRows * (long)_rowSizeInBytes);
            s.Write(new byte[pad], 0, pad);
              }
              s.Flush();
              #endregion

              #region 2) fix the header and write it to the main stream
              myHeader.RemoveCard("NAXIS2");
              myHeader.SetNaxis(2, nRows);
              // shoehorn correct heap information into header
              // PCOUNT, THEAP, and TFORMn
              // fix NAXIS1
              if(_hasStrings)
              {
            long theap = (long)nRows * (long)_rowSizeInBytes;
            int pad = FitsUtil.Padding(theap + heapS.Position);
            int pcount = (int)heapS.Position + pad;
            // here we correct for swapping out actual strings with heap indices/lengths
            myHeader.RemoveCard("NAXIS1");
            myHeader.InsertCard(new HeaderCard("NAXIS1", _rowSizeInBytes, null), "NAXIS2");
            myHeader.RemoveCard("PCOUNT");
            myHeader.InsertCard(new HeaderCard("PCOUNT", pcount, "Length of heap area in bytes"),
              "GCOUNT");
            myHeader.RemoveCard("THEAP");
            // can't fit a long in here!
            if(pcount > 0 && _writeMode == StringWriteMode.HEAP)
            {
              myHeader.AddValue("THEAP", (int)theap, "Position of heap wrt start of binary table");
            }

            // fix the TFORMn entries for string columns
            IEnumerator ie = null;
            bool found = false;
            for(int i = 0; i < maxColWidths.Length; ++i, found = false)
            {
              if(maxColWidths[i] > -1)
              {
                ie = myHeader.GetEnumerator();
                ie.MoveNext();
                for(int j = 0; !found && ie.Current != null; ++j, ie.MoveNext())
                {
                  if(("TFORM" + (i + 1)).Equals(((DictionaryEntry)ie.Current).Key))
                  {
                    myHeader.RemoveCard(j);
                    myHeader.
                      InsertCard(new HeaderCard("TFORM" + (i + 1),
                      "1PA(" + maxColWidths[i] + ")",
                      null), j);
                    found = true;
                  }
                }
              }
            }
              }
              // rewrite the header
              long heapMark = s.Position;
              s.Seek(headerMark, SeekOrigin.Begin);
              myHeader.Write(s);
              #endregion

              #region 3) write the heap tempfile to the main stream
              if(_hasStrings)
              {
            // calculate the pad
            int pad = FitsUtil.Padding((long)nRows * (long)_rowSizeInBytes + heapS.Position);

            s.Seek(heapMark, SeekOrigin.Begin);

            // write heap to the main stream
            heapS.Seek(0, SeekOrigin.Begin);
            for(int nRead = heapS.Read(_buf, 0, _buf.Length); nRead > 0;)
            {
              s.Write(_buf, 0, nRead);
              nRead = heapS.Read(_buf, 0, _buf.Length);
            }
            heapS.Close();
            File.Delete(heapFilename);

            // pad the file
            s.Write(new byte[pad], 0, pad);
              }
              #endregion
        }