#if MYSQL_6_10

// Copyright ?2004, 2016, 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;
using System.Text;
using System.Collections.Generic;
using System.Data;
using Externals.MySql.Data.Common;

namespace Externals.MySql.Data.MySqlClient
{
    /// <summary>
    /// Summary description for PreparedStatement.
    /// </summary>
    internal class PreparableStatement : Statement
    {
        BitArray _nullMap;
        readonly List<MySqlParameter> _parametersToSend = new List<MySqlParameter>();
        MySqlPacket _packet;
        int _dataPosition;
        int _nullMapPosition;

        public PreparableStatement(MySqlCommand command, string text)
          : base(command, text)
        {
        }

        #region Properties

        public int ExecutionCount { get; set; }

        public bool IsPrepared => StatementId > 0;

        public int StatementId { get; private set; }

        #endregion

        public virtual void Prepare()
        {
            // strip out names from parameter markers
            string text;
            List<string> parameterNames = PrepareCommandText(out text);

            // ask our connection to send the prepare command
            MySqlField[] paramList = null;
            StatementId = Driver.PrepareStatement(text, ref paramList);

            // now we need to assign our field names since we stripped them out
            // for the prepare
            for (int i = 0; i < parameterNames.Count; i++)
            {
                //paramList[i].ColumnName = (string) parameter_names[i];
                string parameterName = (string)parameterNames[i];
                MySqlParameter p = Parameters.GetParameterFlexible(parameterName, false);
                if (p == null)
                    throw new InvalidOperationException(
                        String.Format(Resources.ParameterNotFoundDuringPrepare, parameterName));
                p.Encoding = paramList[i].Encoding;
                _parametersToSend.Add(p);
            }

            // now prepare our null map
            int numNullBytes = 0;
            if (paramList != null && paramList.Length > 0)
            {
                _nullMap = new BitArray(paramList.Length);
                numNullBytes = (_nullMap.Length + 7) / 8;
            }

            _packet = new MySqlPacket(Driver.Encoding);

            // write out some values that do not change run to run
            _packet.WriteByte(0);
            _packet.WriteInteger(StatementId, 4);
            _packet.WriteByte((byte)0); // flags; always 0 for 4.1
            _packet.WriteInteger(1, 4); // interation count; 1 for 4.1
            _nullMapPosition = _packet.Position;
            _packet.Position += numNullBytes;  // leave room for our null map
            _packet.WriteByte(1); // rebound flag
                                  // write out the parameter types
            foreach (MySqlParameter p in _parametersToSend)
                _packet.WriteInteger(p.GetPSType(), 2);
            _dataPosition = _packet.Position;
        }

        public override void Execute()
        {
            // if we are not prepared, then call down to our base
            if (!IsPrepared)
            {
                base.Execute();
                return;
            }

            //TODO: support long data here
            // create our null bitmap

            // we check this because Mono doesn't ignore the case where nullMapBytes
            // is zero length.
            //            if (nullMapBytes.Length > 0)
            //          {
            //            byte[] bits = packet.Buffer;
            //          nullMap.CopyTo(bits, 
            //        nullMap.CopyTo(nullMapBytes, 0);

            // start constructing our packet
            //            if (Parameters.Count > 0)
            //              nullMap.CopyTo(packet.Buffer, nullMapPosition);
            //if (parameters != null && parameters.Count > 0)
            //else
            //	packet.WriteByte( 0 );
            //TODO:  only send rebound if parms change

            // now write out all non-null values
            _packet.Position = _dataPosition;
            for (int i = 0; i < _parametersToSend.Count; i++)
            {
                MySqlParameter p = _parametersToSend[i];
                _nullMap[i] = (p.Value == DBNull.Value || p.Value == null) ||
                    p.Direction == ParameterDirection.Output;
                if (_nullMap[i]) continue;
                _packet.Encoding = p.Encoding;
                p.Serialize(_packet, true, Connection.Settings);
            }
            _nullMap?.CopyTo(_packet.Buffer, _nullMapPosition);

            ExecutionCount++;

            Driver.ExecuteStatement(_packet);
        }

        public override bool ExecuteNext()
        {
            if (!IsPrepared)
                return base.ExecuteNext();
            return false;
        }

        /// <summary>
        /// Prepares CommandText for use with the Prepare method
        /// </summary>
        /// <returns>Command text stripped of all paramter names</returns>
        /// <remarks>
        /// Takes the output of TokenizeSql and creates a single string of SQL
        /// that only contains '?' markers for each parameter.  It also creates
        /// the parameterMap array list that includes all the paramter names in the
        /// order they appeared in the SQL
        /// </remarks>
        private List<string> PrepareCommandText(out string stripped_sql)
        {
            StringBuilder newSQL = new StringBuilder();
            List<string> parameterMap = new List<string>();

            int startPos = 0;
            string sql = ResolvedCommandText;
            MySqlTokenizer tokenizer = new MySqlTokenizer(sql);
            string parameter = tokenizer.NextParameter();
            while (parameter != null)
            {
                if (parameter.IndexOf(StoredProcedure.ParameterPrefix) == -1)
                {
                    newSQL.Append(sql.Substring(startPos, tokenizer.StartIndex - startPos));
                    newSQL.Append("?");
                    parameterMap.Add(parameter);
                    startPos = tokenizer.StopIndex;
                }
                parameter = tokenizer.NextParameter();
            }
            newSQL.Append(sql.Substring(startPos));
            stripped_sql = newSQL.ToString();
            return parameterMap;
        }

        public virtual void CloseStatement()
        {
            if (!IsPrepared) return;

            Driver.CloseStatement(StatementId);
            StatementId = 0;
        }
    }
}

#endif