#if MYSQL_6_10

// Copyright © 2009, 2018, Oracle and/or its affiliates. All rights reserved.
//
// MySQL Connector/NET is licensed under the terms of the GPLv2
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most 
// MySQL Connectors. There are special exceptions to the terms and 
// conditions of the GPLv2 as it is applied to this software, see the 
// FLOSS License Exception
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
//
// This program is free software; you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published 
// by the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
//
// You should have received a copy of the GNU General Public License along 
// with this program; if not, write to the Free Software Foundation, Inc., 
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using Externals.MySql.Data.Types;

namespace Externals.MySql.Data.MySqlClient
{
    internal class ResultSet
    {
        private Driver _driver;
        private bool[] _uaFieldsUsed;
        private Dictionary<string, int> _fieldHashCi;
        private int _rowIndex;
        private bool _readDone;
        private bool _isSequential;
        private int _seqIndex;
        private readonly int _statementId;
        private bool _cached;
        private List<IMySqlValue[]> _cachedValues;

        public ResultSet(int affectedRows, long insertedId)
        {
            AffectedRows = affectedRows;
            InsertedId = insertedId;
            _readDone = true;
        }

        public ResultSet(Driver d, int statementId, int numCols)
        {
            AffectedRows = -1;
            InsertedId = -1;
            _driver = d;
            _statementId = statementId;
            _rowIndex = -1;
            LoadColumns(numCols);
            IsOutputParameters = IsOutputParameterResultSet();
            HasRows = GetNextRow();
            _readDone = !HasRows;
        }

        #region Properties

        public bool HasRows { get; }

        public int Size => Fields?.Length ?? 0;

        public MySqlField[] Fields { get; private set; }

        public IMySqlValue[] Values { get; private set; }

        public bool IsOutputParameters { get; set; }

        public int AffectedRows { get; private set; }

        public long InsertedId { get; private set; }

        public int TotalRows { get; private set; }

        public int SkippedRows { get; private set; }

        public bool Cached
        {
            get { return _cached; }
            set
            {
                _cached = value;
                if (_cached && _cachedValues == null)
                    _cachedValues = new List<IMySqlValue[]>();
            }
        }

        #endregion

        /// <summary>
        /// return the ordinal for the given column name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetOrdinal(string name)
        {
            int ordinal;

            // quick hash lookup using CI hash
            if (_fieldHashCi.TryGetValue(name, out ordinal))
                return ordinal;

            // Throw an exception if the ordinal cannot be found.
            throw new IndexOutOfRangeException(
                String.Format(Resources.CouldNotFindColumnName, name));
        }

        /// <summary>
        /// Retrieve the value as the given column index
        /// </summary>
        /// <param name="index">The column value to retrieve</param>
        /// <returns>The value as the given column</returns>
        public IMySqlValue this[int index]
        {
            get
            {
                if (_rowIndex < 0)
                    throw new MySqlException(Resources.AttemptToAccessBeforeRead);

                // keep count of how many columns we have left to access
                _uaFieldsUsed[index] = true;

                if (_isSequential && index != _seqIndex)
                {
                    if (index < _seqIndex)
                        throw new MySqlException(Resources.ReadingPriorColumnUsingSeqAccess);
                    while (_seqIndex < (index - 1))
                        _driver.SkipColumnValue(Values[++_seqIndex]);
                    Values[index] = _driver.ReadColumnValue(index, Fields[index], Values[index]);
                    _seqIndex = index;
                }

                return Values[index];
            }
        }

        private bool GetNextRow()
        {
            bool fetched = _driver.FetchDataRow(_statementId, Size);
            if (fetched)
                TotalRows++;
            return fetched;
        }


        public bool NextRow(CommandBehavior behavior)
        {
            if (_readDone)
            {
                if (Cached) return CachedNextRow(behavior);
                return false;
            }

            if ((behavior & CommandBehavior.SingleRow) != 0 && _rowIndex == 0)
                return false;

            _isSequential = (behavior & CommandBehavior.SequentialAccess) != 0;
            _seqIndex = -1;

            // if we are at row index >= 0 then we need to fetch the data row and load it
            if (_rowIndex >= 0)
            {
                bool fetched = false;
                try
                {
                    fetched = GetNextRow();
                }
                catch (MySqlException ex)
                {
                    if (ex.IsQueryAborted)
                    {
                        // avoid hanging on Close()
                        _readDone = true;
                    }
                    throw;
                }

                if (!fetched)
                {
                    _readDone = true;
                    return false;
                }
            }

            if (!_isSequential) ReadColumnData(false);
            _rowIndex++;
            return true;
        }

        private bool CachedNextRow(CommandBehavior behavior)
        {
            if ((behavior & CommandBehavior.SingleRow) != 0 && _rowIndex == 0)
                return false;
            if (_rowIndex == (TotalRows - 1)) return false;
            _rowIndex++;
            Values = _cachedValues[_rowIndex];
            return true;
        }

        /// <summary>
        /// Closes the current resultset, dumping any data still on the wire
        /// </summary>
        public void Close()
        {
            if (!_readDone)
            {

                // if we have rows but the user didn't read the first one then mark it as skipped
                if (HasRows && _rowIndex == -1)
                    SkippedRows++;
                try
                {
                    while (_driver.IsOpen && _driver.SkipDataRow())
                    {
                        TotalRows++;
                        SkippedRows++;
                    }
                }
                catch (System.IO.IOException)
                {
                    // it is ok to eat IO exceptions here, we just want to 
                    // close the result set
                }
                _readDone = true;
            }
            else if (_driver == null)
                CacheClose();

            _driver = null;
            if (Cached) CacheReset();
        }

        private void CacheClose()
        {
            SkippedRows = TotalRows - _rowIndex - 1;
        }

        private void CacheReset()
        {
            if (!Cached) return;
            _rowIndex = -1;
            AffectedRows = -1;
            InsertedId = -1;
            SkippedRows = 0;
        }

        public bool FieldRead(int index)
        {
            Debug.Assert(Size > index);
            return _uaFieldsUsed[index];
        }

        public void SetValueObject(int i, IMySqlValue valueObject)
        {
            Debug.Assert(Values != null);
            Debug.Assert(i < Values.Length);
            Values[i] = valueObject;
        }

        private bool IsOutputParameterResultSet()
        {
            if (_driver.HasStatus(ServerStatusFlags.OutputParameters)) return true;

            if (Fields.Length == 0) return false;

            for (int x = 0; x < Fields.Length; x++)
                if (!Fields[x].ColumnName.StartsWith("@" + StoredProcedure.ParameterPrefix, StringComparison.OrdinalIgnoreCase)) return false;
            return true;
        }

        /// <summary>
        /// Loads the column metadata for the current resultset
        /// </summary>
        private void LoadColumns(int numCols)
        {
            Fields = _driver.GetColumns(numCols);

            Values = new IMySqlValue[numCols];
            _uaFieldsUsed = new bool[numCols];
            _fieldHashCi = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

            for (int i = 0; i < Fields.Length; i++)
            {
                string columnName = Fields[i].ColumnName;
                if (!_fieldHashCi.ContainsKey(columnName))
                    _fieldHashCi.Add(columnName, i);
                Values[i] = Fields[i].GetValueObject();
            }
        }

        private void ReadColumnData(bool outputParms)
        {
            for (int i = 0; i < Size; i++)
                Values[i] = _driver.ReadColumnValue(i, Fields[i], Values[i]);

            // if we are caching then we need to save a copy of this row of data values
            if (Cached)
                _cachedValues.Add((IMySqlValue[])Values.Clone());

            // we don't need to worry about caching the following since you won't have output
            // params with TableDirect commands
            if (!outputParms) return;

            bool rowExists = _driver.FetchDataRow(_statementId, Fields.Length);
            _rowIndex = 0;
            if (rowExists)
                throw new MySqlException(Resources.MoreThanOneOPRow);
        }
    }
}

#endif