/// <summary>
        /// Saves this <see cref="CsvTable"/> on disk with the specified path and with compression
        /// if specified.
        /// </summary>
        /// <param name="path">Path of the .csv to save to.</param>
        /// <param name="compression">Whether to compress the file or not.</param>
        ///
        /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="compression"/> is not either <see cref="CsvTableCompression.Compressed"/> or <see cref="CsvTableCompression.Uncompressed"/>
        /// </exception>
        /// <exception cref="ObjectDisposedException"><see cref="CsvTable"/> object was disposed.</exception>
        public void Save(string path, CsvTableCompression compression)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(null, "Cannot access CsvTable object because it was disposed.");
            }
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }
            if (compression < CsvTableCompression.Compressed || compression > CsvTableCompression.Uncompressed)
            {
                throw new ArgumentException("Unexpected compression type.", "compression");
            }

            // Write column names.
            var builder = new StringBuilder();

            for (int i = 0; i < Table.Columns.Count - 1; i++)
            {
                builder.Append(Columns[i].ColumnName + ",");
            }
            builder.AppendLine(Columns[Columns.Count - 1].ColumnName);

            // Write column types.
            for (int i = 0; i < Table.Columns.Count; i++)
            {
                // Append "\r\n"(new line) if last in array else append ",".
                var format   = i == Table.Columns.Count - 1 ? "{0}\r\n" : "{0},";
                var dataType = Table.Columns[i].DataType;
                if (dataType == typeof(int))
                {
                    builder.AppendFormat(format, "int");
                }
                else
                {
                    builder.AppendFormat(format, dataType.Name);
                }
            }

            // Write all rows.
            for (int i = 0; i < Table.Rows.Count; i++)
            {
                builder.AppendLine(string.Join(",", Table.Rows[i].ItemArray));
            }

            // Compress it if specified.
            if (compression == CsvTableCompression.Compressed)
            {
                using (var inStream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
                    using (var outStream = (MemoryStream)CompressInternal(inStream))
                    {
                        File.WriteAllBytes(path, outStream.ToArray());
                    }
            }
            else if (compression == CsvTableCompression.Uncompressed)
            {
                File.WriteAllText(path, builder.ToString());
            }
        }
        /// <summary>
        /// Reads the specified .csv file from a byte array and decompresses it if specified.
        /// </summary>
        /// <param name="bytes">Bytes of the .csv file.</param>
        /// <param name="compression">Whether the .csv file is compressed or not.</param>
        /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is null.</exception>
        /// <exception cref="CsvException">Unexpected data type in CSV table.</exception>
        /// <exception cref="ObjectDisposedException"><see cref="CsvTable"/> object was disposed.</exception>
        public void Load(byte[] bytes, CsvTableCompression compression)
        {
            CheckDisposed();

            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }

            var stream = new MemoryStream(bytes);

            LoadInternal(stream, compression);
        }
        /// <summary>
        /// Reads the specified .csv file from disk and decompresses it if specified.
        /// </summary>
        /// <param name="path">Path to the .csv file.</param>
        /// <param name="compression">Compression kind of the table.</param>
        /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
        /// <exception cref="CsvException">Unexpected data type in CSV table.</exception>
        /// <exception cref="ObjectDisposedException"><see cref="CsvTable"/> object was disposed.</exception>
        public void Load(string path, CsvTableCompression compression)
        {
            CheckDisposed();

            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            var fileStream = new FileStream(path, FileMode.Open);

            LoadInternal(fileStream, compression);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CsvTable"/> class and decompresses
        /// the specified .csv file if compressed, then reads file and parses it.
        /// </summary>
        /// <param name="path">Path to the .csv file.</param>
        /// <param name="compression">Compression kind of the table.</param>
        /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
        /// <exception cref="CsvException">Unexpected data type in CSV table.</exception>
        public CsvTable(string path, CsvTableCompression compression)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            _disposed = false;

            // Set file name without the extension as the table name.
            var tableName = Path.GetFileNameWithoutExtension(path);

            _table = new DataTable(tableName);

            var fileStream = new FileStream(path, FileMode.Open);

            LoadInternal(fileStream, compression);
        }
        // Loads the specified CSV file stream and compression kind, into the DataTable.
        private void LoadInternal(Stream stream, CsvTableCompression compression)
        {
            // Detect compression if it compression is to Auto.
            if (compression == CsvTableCompression.Auto)
            {
                compression = DetectCompression(stream);
            }

            // If we detected that the CSV file is compressed or it was already specified,
            // we decompress it.
            if (compression == CsvTableCompression.Compressed)
            {
                var newStream = DecompressInternal(stream);

                stream.Seek(0, SeekOrigin.Begin);
                // Just in case uncompressed data is smaller than compressed data.
                stream.SetLength(newStream.Length);
                newStream.CopyTo(stream);
            }

            // stream will be disposed as well when reader is disposed.
            using (var reader = (TextReader) new StreamReader(stream))
            {
                stream.Seek(0, SeekOrigin.Begin);

                var strTable = ParseTable(reader);
                // First row of CSV files are the property names.
                var rowNames = strTable[0];
                if (rowNames == null)
                {
                    throw new CsvException("CSV file does not contain the names row, that is, the first row.");
                }

                // Second row of the CSV files are the property types.
                var rowTypes = strTable[1];
                if (rowTypes == null)
                {
                    throw new CsvException("CSV file does not contain the types row, that is, the second row.");
                }

                // Width of table is determined by the first row.
                var tableWidth = rowNames.Length;
                // Set the columns of the DataTable according the first and second row
                SetColumns(rowNames, rowTypes);

                // i = 2, because we want to skip the first 2 rows.
                for (int i = 2; i < strTable.Length; i++)
                {
                    // Raw string row from the file.
                    var row = strTable[i];
                    // DataRow that we'll add to the Table.
                    var tableRow = Table.NewRow();
                    Table.Rows.Add(tableRow);

                    for (int j = 0; j < row.Length; j++)
                    {
                        var column = row[j];
                        // If the string is null (should not happen) or empty,
                        // we set its table value to be DBNull.Value.
                        if (string.IsNullOrEmpty(column))
                        {
                            tableRow[j] = DBNull.Value;
                        }
                        else
                        {
                            tableRow[j] = column;
                        }
                    }
                }
            }
        }