/***************************************************************************
 *
 * CurlS#arp
 *
 * Copyright (c) 2013-2017 Dr. Masroor Ehsan (masroore@gmail.com)
 * Portions copyright (c) 2004, 2005 Jeff Phillips (jeff@jeffp.net)
 *
 * This software is licensed as described in the file LICENSE, which you
 * should have received as part of this distribution.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of this Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the LICENSE file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
 * ANY KIND, either express or implied.
 *
 **************************************************************************/

using System;
using System.Runtime.InteropServices;
using CurlSharp.Callbacks;
using CurlSharp.Enums;

namespace CurlSharp
{
    /// <summary>
    ///     Implements the <c>curl_easy_xxx</c> API.
    /// </summary>
    /// <remarks>
    ///     This is the most important class in <c>libcurl.NET</c>. It wraps a
    ///     <c>CURL*</c> handle and provides delegates through which callbacks
    ///     (such as <c>CurlWriteCallback</c> and <c>CurlReadCallback</c>)
    ///     are implemented.
    /// </remarks>
    public class CurlEasy : IDisposable
    {
        // constants (used internally only)
        private const int CURLOPTTYPE_OBJECTPOINT = 10000;
        private const int CURLOPTTYPE_FUNCTIONPOINT = 20000;
        private const int CURLOPTTYPE_OFF_T = 30000;
        private const int CURLINFO_STRING = 0x100000;
        private const int CURLINFO_LONG = 0x200000;
        private const int CURLINFO_DOUBLE = 0x300000;
        private const int CURLINFO_SLIST = 0x400000;
#if USE_LIBCURLSHIM
        private readonly IntPtr _pMyStrings;
#endif
        private bool _autoReferer;
        private int _bufferSize;
        private string _caInfo;
        private string _caPath;
        private CurlClosePolicy _closePolicy;
        private int _connectTimeout;
        private string _cookie;
        private string _cookieFile;
        private string _cookieJar;
        private bool _cookieSession;
        private CurlShare _curlShare;
        private string _customRequest;
        private object _debugData;
        private int _dnsCacheTimeout;
        private bool _dnsUseGlobalCache;
        private string _egdSocket;
        private string _encoding;
        private string _errorBuffer;
        private bool _failOnError;
        private bool _filetime;
        private bool _followLocation;
        private bool _forbidReuse;
        private bool _freshConnect;
        private string _ftpAccount;
        private bool _ftpAppend;
        private CurlFtpAuth _ftpAuth;
        private bool _ftpCreateMissingDirs;
        private bool _ftpListOnly;
        private string _ftpPort;
        private int _ftpResponseTimeout;
        private bool _ftpSkipPasvIp;
        private CurlFtpSsl _ftpSsl;
        private bool _ftpUseEprt;
        private bool _ftpUseEpsv;
        private GCHandle _hThis;
        private object _headerData;
        private CurlHttpAuth _httpAuth;
        private bool _httpGet;
        private CurlHttpMultiPartForm _httpMultiPartForm;
        private bool _httpProxyTunnel;
        private CurlHttpVersion _httpVersion;
        private bool _ignoreContentLength;
        private long _infileSize;
        private string _interface;
        private object _ioctlData;
        private string _krb4Level;
        private int _lowSpeedLimit;
        private int _lowSpeedTime;
        private int _maxConnects;
        private long _maxFileSize;
        private int _maxRedirs;
        private string _netRcFile;
        private bool _noBody;
        private bool _noProgress;
        private bool _noSignal;
        private IntPtr _pCurl;
#if USE_LIBCURLSHIM
        private NativeMethods._ShimDebugCallback _pcbDebug;
        private NativeMethods._ShimHeaderCallback _pcbHeader;
        private NativeMethods._ShimIoctlCallback _pcbIoctl;
        private NativeMethods._ShimProgressCallback _pcbProgress;
        private NativeMethods._ShimReadCallback _pcbRead;
        private NativeMethods._ShimSslCtxCallback _pcbSslCtx;
        private NativeMethods._ShimWriteCallback _pcbWrite;
        private IntPtr _ptrThis;
#else
        private NativeMethods._CurlGenericCallback _pcbWrite;
        private NativeMethods._CurlGenericCallback _pcbRead;
        private NativeMethods._CurlGenericCallback _pcbHeader;
        private NativeMethods._CurlDebugCallback _pcbDebug;
        private NativeMethods._CurlIoctlCallback _pcbIoctl;
        private NativeMethods._CurlProgressCallback _pcbProgress;
        private NativeMethods._CurlSslCtxCallback _pcbSslCtx;
#endif
        private CurlDebugCallback _pfCurlDebug;
        private CurlHeaderCallback _pfCurlHeader;
        private CurlIoctlCallback _pfCurlIoctl;
        private CurlProgressCallback _pfCurlProgress;
        private CurlReadCallback _pfCurlRead;
        private CurlSslContextCallback _pfCurlSslContext;
        private CurlWriteCallback _pfCurlWrite;
        private int _port;
        private bool _post;
        private int _postFieldSize;
        private string _postFields;
        private object _progressData;
        private string _proxy;
        private int _proxyPort;
        private string _proxyUserPwd;
        private bool _put;
        private string _randomFile;
        private string _range;
        private object _readData;
        private string _referer;
        private int _resumeFrom;
        private string _sourceUrl;
        private string _sslCert;
        private string _sslCertPasswd;
        private string _sslCipherList;
        private object _sslContextData;
        private string _sslEngine;
        private bool _sslEngineDefault;
        private string _sslKey;
        private string _sslKeyPasswd;
        private bool _sslVerifyPeer;
        private bool _sslVerifyHost;
        private bool _tcpNoDelay;
        private int _timeValue;
        private int _timeout;
        private bool _transferText;
        private bool _unrestrictedAuth;
        private bool _upload;
        private string _url;
        private string _userAgent;
        private string _userPwd;
        private bool _verbose;
        private object _writeData;
        private string _writeInfo;

        /// <summary>
        ///     Constructor
        /// </summary>
        /// <exception cref="System.InvalidOperationException">
        ///     This is thrown
        ///     if <see cref="Curl" /> hasn't bee properly initialized.
        /// </exception>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlEasy()
        {
            Curl.EnsureCurl();
            _pCurl = NativeMethods.curl_easy_init();
            ensureHandle();
            NativeMethods.curl_easy_setopt(_pCurl, CurlOption.NoSignal, 1L);
            NativeMethods.curl_easy_setopt(_pCurl, CurlOption.NoProgress, IntPtr.Zero);
#if USE_LIBCURLSHIM
            _pMyStrings = NativeMethods.curl_shim_alloc_strings();
#endif
            resetPrivateVariables();
            installDelegates();
        }

        private CurlEasy(CurlEasy from)
        {
            _pCurl = NativeMethods.curl_easy_duphandle(from._pCurl);
            ensureHandle();
#if USE_LIBCURLSHIM
            _pMyStrings = NativeMethods.curl_shim_alloc_strings();
#endif
            resetPrivateVariables();
            installDelegates();
        }

        public object Private { get; set; }

        public object WriteData
        {
            get { return _writeData; }
            set
            {
                _writeData = value;
#if !USE_LIBCURLSHIM
                setWriteData(value);
#endif
            }
        }

#if !USE_LIBCURLSHIM
        private IntPtr _curlWriteData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnWriteCallback.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setWriteData(object data)
        {
            freeHandle(ref _curlWriteData);

            _curlWriteData = getHandle(data);
            return setCurlOpt(_curlWriteData, CurlOption.WriteData);
        }

        private CurlCode setCurlOpt(IntPtr data, CurlOption opt)
        {
            var retCode = NativeMethods.curl_easy_setopt(_pCurl, opt, data);
            setLastError(retCode, opt);
            return retCode;
        }

        private IntPtr _curlReadData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnReadCallback.
        ///     Use <see cref="getObject" /> to convert the passed IntPtr back into the object, then cast.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setReadData(object data)
        {
            _curlReadData = getHandle(data);
            return setCurlOpt(_curlReadData, CurlOption.ReadData);
        }

        private IntPtr _curlProgressData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnProgressCallback.
        ///     Use <see cref="getObject" /> to convert the passed IntPtr back into the object, then cast.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setProgressData(object data)
        {
            _curlProgressData = getHandle(data);
            return setCurlOpt(_curlProgressData, CurlOption.ProgressData);
        }

        private IntPtr _curlHeaderData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnHeaderCallback.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setHeaderData(object data)
        {
            _curlHeaderData = getHandle(data);
            return setCurlOpt(_curlHeaderData, CurlOption.HeaderData);
        }

        private IntPtr _curlDebugData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnDebugCallback.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setDebugData(object data)
        {
            _curlDebugData = getHandle(data);
            return setCurlOpt(_curlDebugData, CurlOption.DebugData);
        }

        private IntPtr _curlSslCtxData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnSslCtxCallback.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setSslCtxData(object data)
        {
            _curlSslCtxData = getHandle(data);
            return setCurlOpt(_curlSslCtxData, CurlOption.SslCtxData);
        }

        private IntPtr _curlIoctlData = IntPtr.Zero;

        /// <summary>
        ///     Object to pass to OnIoctlCallback.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private CurlCode setIoctlData(object data)
        {
            _curlIoctlData = getHandle(data);
            return setCurlOpt(_curlIoctlData, CurlOption.IoctlData);
        }
#endif

        public object ReadData
        {
            get { return _readData; }
            set
            {
                _readData = value;
#if !USE_LIBCURLSHIM
                setReadData(value);
#endif
            }
        }

        public object ProgressData
        {
            get { return _progressData; }
            set
            {
                _progressData = value;
#if !USE_LIBCURLSHIM
                setProgressData(value);
#endif
            }
        }

        public object DebugData
        {
            get { return _debugData; }
            set
            {
                _debugData = value;
#if !USE_LIBCURLSHIM
                setDebugData(value);
#endif
            }
        }

        public object HeaderData
        {
            get { return _headerData; }
            set
            {
                _headerData = value;
#if !USE_LIBCURLSHIM
                setHeaderData(value);
#endif
            }
        }

        public object SslCtxData
        {
            get { return _sslContextData; }
            set
            {
                _sslContextData = value;
#if !USE_LIBCURLSHIM
                setSslCtxData(value);
#endif
            }
        }

        public object IoctlData
        {
            get { return _ioctlData; }
            set
            {
                _ioctlData = value;
#if !USE_LIBCURLSHIM
                setIoctlData(value);
#endif
            }
        }

        public CurlShare Share
        {
            get { return _curlShare; }
            set
            {
                _curlShare = value;
                setShareObject();
            }
        }

        public CurlHttpMultiPartForm HttpPost
        {
            get { return _httpMultiPartForm; }
            set
            {
                _httpMultiPartForm = value;
                setMultiPartFormObject();
            }
        }

        public CurlSlist HttpHeader
        {
            set { setSlistObject(CurlOption.HttpHeader, value); }
        }

        public CurlSlist Prequote
        {
            set { setSlistObject(CurlOption.Prequote, value); }
        }

        public CurlSlist Quote
        {
            set { setSlistObject(CurlOption.Quote, value); }
        }

        public CurlSlist Postquote
        {
            set { setSlistObject(CurlOption.Postquote, value); }
        }

        public CurlSlist SourceQuote
        {
            set { setSlistObject(CurlOption.SourceQuote, value); }
        }

        public CurlSlist Http200Aliases
        {
            set { setSlistObject(CurlOption.Http200Aliases, value); }
        }

        public CurlFtpAuth FtpAuth
        {
            get { return _ftpAuth; }
            set
            {
                _ftpAuth = value;
                var l = Convert.ToInt32(value);
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.FtpSslAuth, (IntPtr) l),
                    CurlOption.FtpSslAuth);
            }
        }

        public CurlHttpVersion HttpVersion
        {
            get { return _httpVersion; }
            set
            {
                _httpVersion = value;
                var l = Convert.ToInt32(value);
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.HttpVersion, (IntPtr) l),
                    CurlOption.HttpVersion);
            }
        }

        public CurlHttpAuth HttpAuth
        {
            get { return _httpAuth; }
            set
            {
                _httpAuth = value;
                var l = Convert.ToInt32(value);
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.HttpAuth, (IntPtr) l),
                    CurlOption.HttpAuth);
            }
        }

        public CurlFtpSsl FtpSsl
        {
            get { return _ftpSsl; }
            set
            {
                _ftpSsl = value;
                var l = Convert.ToInt32(value);
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.FtpSsl, (IntPtr) l),
                    CurlOption.FtpSsl);
            }
        }

        public CurlClosePolicy ClosePolicy
        {
            get { return _closePolicy; }
            set
            {
                _closePolicy = value;
                var l = Convert.ToInt32(value);
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.ClosePolicy, (IntPtr) l),
                    CurlOption.ClosePolicy);
            }
        }

        public CurlWriteCallback WriteFunction
        {
            get { return _pfCurlWrite; }
            set { setFunctionOptions(CurlOption.WriteFunction, value); }
        }

        public CurlReadCallback ReadFunction
        {
            get { return _pfCurlRead; }
            set { setFunctionOptions(CurlOption.ReadFunction, value); }
        }

        public CurlHeaderCallback HeaderFunction
        {
            get { return _pfCurlHeader; }
            set { setFunctionOptions(CurlOption.HeaderFunction, value); }
        }

        public CurlDebugCallback DebugFunction
        {
            get { return _pfCurlDebug; }
            set { setFunctionOptions(CurlOption.DebugFunction, value); }
        }

        public CurlProgressCallback ProgressFunction
        {
            get { return _pfCurlProgress; }
            set { setFunctionOptions(CurlOption.ProgressFunction, value); }
        }

        public CurlIoctlCallback IoctlFunction
        {
            get { return _pfCurlIoctl; }
            set { setFunctionOptions(CurlOption.IoctlFunction, value); }
        }

        public CurlSslContextCallback SslContextFunction
        {
            get { return _pfCurlSslContext; }
            set { setFunctionOptions(CurlOption.SslCtxFunction, value); }
        }

        public string LastErrorDescription { get; private set; }

        public bool NoProgress
        {
            get { return _noProgress; }
            set { setBoolOption(CurlOption.NoProgress, ref _noProgress, value); }
        }

        public bool NoBody
        {
            get { return _noBody; }
            set { setBoolOption(CurlOption.NoBody, ref _noBody, value); }
        }

        public bool FailOnError
        {
            get { return _failOnError; }
            set { setBoolOption(CurlOption.FailOnError, ref _failOnError, value); }
        }

        public bool Upload
        {
            get { return _upload; }
            set { setBoolOption(CurlOption.Upload, ref _upload, value); }
        }

        public bool Post
        {
            get { return _post; }
            set { setBoolOption(CurlOption.Post, ref _post, value); }
        }

        public bool FtpListOnly
        {
            get { return _ftpListOnly; }
            set { setBoolOption(CurlOption.FtpListOnly, ref _ftpListOnly, value); }
        }

        public bool FtpAppend
        {
            get { return _ftpAppend; }
            set { setBoolOption(CurlOption.FtpAppend, ref _ftpAppend, value); }
        }

        public bool FollowLocation
        {
            get { return _followLocation; }
            set { setBoolOption(CurlOption.FollowLocation, ref _followLocation, value); }
        }

        public bool TransferText
        {
            get { return _transferText; }
            set { setBoolOption(CurlOption.TransferText, ref _transferText, value); }
        }

        public bool Put
        {
            get { return _put; }
            set { setBoolOption(CurlOption.Put, ref _put, value); }
        }

        public bool HttpProxyTunnel
        {
            get { return _httpProxyTunnel; }
            set { setBoolOption(CurlOption.HttpProxyTunnel, ref _httpProxyTunnel, value); }
        }

        public bool SslVerifyPeer
        {
            get { return _sslVerifyPeer; }
            set { setBoolOption(CurlOption.SslVerifyPeer, ref _sslVerifyPeer, value); }
        }

        public bool SslVerifyhost
        {
            get { return _sslVerifyHost; }
            set { setBoolOption(CurlOption.SslVerifyhost, ref _sslVerifyHost, value); }
        }

        public bool FreshConnect
        {
            get { return _freshConnect; }
            set { setBoolOption(CurlOption.FreshConnect, ref _freshConnect, value); }
        }

        public bool ForbidReuse
        {
            get { return _forbidReuse; }
            set { setBoolOption(CurlOption.ForbidReuse, ref _forbidReuse, value); }
        }

        public bool HttpGet
        {
            get { return _httpGet; }
            set { setBoolOption(CurlOption.HttpGet, ref _httpGet, value); }
        }

        public bool FtpUseEpsv
        {
            get { return _ftpUseEpsv; }
            set { setBoolOption(CurlOption.FtpUseEpsv, ref _ftpUseEpsv, value); }
        }

        public bool Filetime
        {
            get { return _filetime; }
            set { setBoolOption(CurlOption.Filetime, ref _filetime, value); }
        }

        public bool UnrestrictedAuth
        {
            get { return _unrestrictedAuth; }
            set { setBoolOption(CurlOption.UnrestrictedAuth, ref _unrestrictedAuth, value); }
        }

        public bool FtpUseEprt
        {
            get { return _ftpUseEprt; }
            set { setBoolOption(CurlOption.FtpUseEprt, ref _ftpUseEprt, value); }
        }

        public bool AutoReferer
        {
            get { return _autoReferer; }
            set { setBoolOption(CurlOption.AutoReferer, ref _autoReferer, value); }
        }

        public bool CookieSession
        {
            get { return _cookieSession; }
            set { setBoolOption(CurlOption.CookieSession, ref _cookieSession, value); }
        }

        public bool SslEngineDefault
        {
            get { return _sslEngineDefault; }
            set { setBoolOption(CurlOption.SslEngineDefault, ref _sslEngineDefault, value); }
        }

        public bool DnsUseGlobalCache
        {
            get { return _dnsUseGlobalCache; }
            set { setBoolOption(CurlOption.DnsUseGlobalCache, ref _dnsUseGlobalCache, value); }
        }

        public bool NoSignal
        {
            get { return _noSignal; }
            set { setBoolOption(CurlOption.NoSignal, ref _noSignal, value); }
        }

        public bool FtpCreateMissingDirs
        {
            get { return _ftpCreateMissingDirs; }
            set { setBoolOption(CurlOption.FtpCreateMissingDirs, ref _ftpCreateMissingDirs, value); }
        }

        public bool TcpNoDelay
        {
            get { return _tcpNoDelay; }
            set { setBoolOption(CurlOption.TcpNoDelay, ref _tcpNoDelay, value); }
        }

        public bool IgnoreContentLength
        {
            get { return _ignoreContentLength; }
            set { setBoolOption(CurlOption.IgnoreContentLength, ref _ignoreContentLength, value); }
        }

        public bool FtpSkipPasvIp
        {
            get { return _ftpSkipPasvIp; }
            set { setBoolOption(CurlOption.FtpSkipPasvIp, ref _ftpSkipPasvIp, value); }
        }

        public int Port
        {
            get { return _port; }
            set { setIntOption(CurlOption.Port, ref _port, value); }
        }

        public int Timeout
        {
            get { return _timeout; }
            set { setIntOption(CurlOption.Timeout, ref _timeout, value); }
        }

        public int LowSpeedLimit
        {
            get { return _lowSpeedLimit; }
            set { setIntOption(CurlOption.LowSpeedLimit, ref _lowSpeedLimit, value); }
        }

        public int LowSpeedTime
        {
            get { return _lowSpeedTime; }
            set { setIntOption(CurlOption.LowSpeedTime, ref _lowSpeedTime, value); }
        }

        public int ResumeFrom
        {
            get { return _resumeFrom; }
            set { setIntOption(CurlOption.ResumeFrom, ref _resumeFrom, value); }
        }

        public int TimeValue
        {
            get { return _timeValue; }
            set { setIntOption(CurlOption.TimeValue, ref _timeValue, value); }
        }

        public int ProxyPort
        {
            get { return _proxyPort; }
            set { setIntOption(CurlOption.ProxyPort, ref _proxyPort, value); }
        }

        public int PostFieldSize
        {
            get { return _postFieldSize; }
            set { setIntOption(CurlOption.PostFieldSize, ref _postFieldSize, value); }
        }

        public int MaxRedirs
        {
            get { return _maxRedirs; }
            set { setIntOption(CurlOption.MaxRedirs, ref _maxRedirs, value); }
        }

        public int MaxConnects
        {
            get { return _maxConnects; }
            set { setIntOption(CurlOption.MaxConnects, ref _maxConnects, value); }
        }

        public int ConnectTimeout
        {
            get { return _connectTimeout; }
            set { setIntOption(CurlOption.ConnectTimeout, ref _connectTimeout, value); }
        }

        public int BufferSize
        {
            get { return _bufferSize; }
            set { setIntOption(CurlOption.BufferSize, ref _bufferSize, value); }
        }

        public int DnsCacheTimeout
        {
            get { return _dnsCacheTimeout; }
            set { setIntOption(CurlOption.DnsCacheTimeout, ref _dnsCacheTimeout, value); }
        }

        public int FtpResponseTimeout
        {
            get { return _ftpResponseTimeout; }
            set { setIntOption(CurlOption.FtpResponseTimeout, ref _ftpResponseTimeout, value); }
        }

        public long InfileSize
        {
            get { return _infileSize; }
            set { setLongOption(CurlOption.InfileSize, ref _infileSize, value); }
        }

        public long MaxFileSize
        {
            get { return _maxFileSize; }
            set { setLongOption(CurlOption.MaxFileSize, ref _maxFileSize, value); }
        }

        public string Url
        {
            get { return _url; }
            set { setStringOption(CurlOption.Url, out _url, value); }
        }

        public string Proxy
        {
            get { return _proxy; }
            set { setStringOption(CurlOption.Proxy, out _proxy, value); }
        }

        public string UserPwd
        {
            get { return _userPwd; }
            set { setStringOption(CurlOption.UserPwd, out _userPwd, value); }
        }

        public string ProxyUserPwd
        {
            get { return _proxyUserPwd; }
            set { setStringOption(CurlOption.ProxyUserPwd, out _proxyUserPwd, value); }
        }

        public string Range
        {
            get { return _range; }
            set { setStringOption(CurlOption.Range, out _range, value); }
        }

        public string PostFields
        {
            get { return _postFields; }
            set { setStringOption(CurlOption.PostFields, out _postFields, value); }
        }

        public string Referer
        {
            get { return _referer; }
            set { setStringOption(CurlOption.Referer, out _referer, value); }
        }

        public string FtpPort
        {
            get { return _ftpPort; }
            set { setStringOption(CurlOption.FtpPort, out _ftpPort, value); }
        }

        public string UserAgent
        {
            get { return _userAgent; }
            set { setStringOption(CurlOption.UserAgent, out _userAgent, value); }
        }

        public string Cookie
        {
            get { return _cookie; }
            set { setStringOption(CurlOption.Cookie, out _cookie, value); }
        }

        public string SslCert
        {
            get { return _sslCert; }
            set { setStringOption(CurlOption.SslCert, out _sslCert, value); }
        }

        public string SslCertPasswd
        {
            get { return _sslCertPasswd; }
            set { setStringOption(CurlOption.SslCertPasswd, out _sslCertPasswd, value); }
        }

        public string CustomRequest
        {
            get { return _customRequest; }
            set { setStringOption(CurlOption.CustomRequest, out _customRequest, value); }
        }

        public string Interface
        {
            get { return _interface; }
            set { setStringOption(CurlOption.Interface, out _interface, value); }
        }

        public string Encoding
        {
            get { return _encoding; }
            set { setStringOption(CurlOption.Encoding, out _encoding, value); }
        }

        public string Krb4Level
        {
            get { return _krb4Level; }
            set { setStringOption(CurlOption.Krb4Level, out _krb4Level, value); }
        }

        public string CaInfo
        {
            get { return _caInfo; }
            set { setStringOption(CurlOption.CaInfo, out _caInfo, value); }
        }

        public string RandomFile
        {
            get { return _randomFile; }
            set { setStringOption(CurlOption.RandomFile, out _randomFile, value); }
        }

        public string EgdSocket
        {
            get { return _egdSocket; }
            set { setStringOption(CurlOption.EgdSocket, out _egdSocket, value); }
        }

        public string CookieJar
        {
            get { return _cookieJar; }
            set { setStringOption(CurlOption.CookieJar, out _cookieJar, value); }
        }

        public string CookieFile
        {
            get { return _cookieFile; }
            set { setStringOption(CurlOption.CookieFile, out _cookieFile, value); }
        }

        public string SslCipherList
        {
            get { return _sslCipherList; }
            set { setStringOption(CurlOption.SslCipherList, out _sslCipherList, value); }
        }

        public string WriteInfo
        {
            get { return _writeInfo; }
            set { setStringOption(CurlOption.WriteInfo, out _writeInfo, value); }
        }

        public string CaPath
        {
            get { return _caPath; }
            set { setStringOption(CurlOption.CaPath, out _caPath, value); }
        }

        public string SslKey
        {
            get { return _sslKey; }
            set { setStringOption(CurlOption.SslKey, out _sslKey, value); }
        }

        public string SslEngine
        {
            get { return _sslEngine; }
            set { setStringOption(CurlOption.SslEngine, out _sslEngine, value); }
        }

        public string SslKeyPasswd
        {
            get { return _sslKeyPasswd; }
            set { setStringOption(CurlOption.SslKeyPasswd, out _sslKeyPasswd, value); }
        }

        public string ErrorBuffer
        {
            get { return _errorBuffer; }
            set { setStringOption(CurlOption.ErrorBuffer, out _errorBuffer, value); }
        }

        public string NetRcFile
        {
            get { return _netRcFile; }
            set { setStringOption(CurlOption.NetRcFile, out _netRcFile, value); }
        }

        public string FtpAccount
        {
            get { return _ftpAccount; }
            set { setStringOption(CurlOption.FtpAccount, out _ftpAccount, value); }
        }

        public string SourceUrl
        {
            get { return _sourceUrl; }
            set { setStringOption(CurlOption.SourceUrl, out _sourceUrl, value); }
        }

        public string EffectiveUrl
        {
            get { return getStringInfo(CurlInfo.EffectiveUrl); }
        }

        public string ContentType
        {
            get { return getStringInfo(CurlInfo.ContentType); }
        }

        public int ResponseCode
        {
            get { return getIntInfo(CurlInfo.ResponseCode); }
        }

        public int HeaderSize
        {
            get { return getIntInfo(CurlInfo.HeaderSize); }
        }

        public int RequestSize
        {
            get { return getIntInfo(CurlInfo.RequestSize); }
        }

        public bool Verbose
        {
            get { return _verbose; }
            set { setBoolOption(CurlOption.Verbose, ref _verbose, value); }
        }

        public int HttpAuthAvail
        {
            get { return getIntInfo(CurlInfo.HttpAuthAvail); }
        }

        public int SslVerifyResult
        {
            get { return getIntInfo(CurlInfo.SslVerifyResult); }
        }

        public int RedirectCount
        {
            get { return getIntInfo(CurlInfo.RedirectCount); }
        }

        public int ProxyAuthAvail
        {
            get { return getIntInfo(CurlInfo.ProxyAuthAvail); }
        }

        public int OsErrno
        {
            get { return getIntInfo(CurlInfo.OsErrno); }
        }

        public int NumConnects
        {
            get { return getIntInfo(CurlInfo.NumConnects); }
        }

        public int HttpConnectCode
        {
            get { return getIntInfo(CurlInfo.HttpConnectCode); }
        }

        public DateTime FileTime
        {
            get { return getDateTimeInfo(CurlInfo.Filetime); }
        }

        public double TotalTime
        {
            get { return getDoubleInfo(CurlInfo.TotalTime); }
        }

        public double NameLookupTime
        {
            get { return getDoubleInfo(CurlInfo.NameLookupTime); }
        }

        public double ConnectTime
        {
            get { return getDoubleInfo(CurlInfo.ConnectTime); }
        }

        public double PreTransferTime
        {
            get { return getDoubleInfo(CurlInfo.PreTransferTime); }
        }

        public double SizeUpload
        {
            get { return getDoubleInfo(CurlInfo.SizeUpload); }
        }

        public double SizeDownload
        {
            get { return getDoubleInfo(CurlInfo.SizeDownload); }
        }

        public double SpeedDownload
        {
            get { return getDoubleInfo(CurlInfo.SpeedDownload); }
        }

        public double SpeedUpload
        {
            get { return getDoubleInfo(CurlInfo.SpeedUpload); }
        }

        public double StartTransferTime
        {
            get { return getDoubleInfo(CurlInfo.StartTransferTime); }
        }

        public double ContentLengthDownload
        {
            get { return getDoubleInfo(CurlInfo.ContentLengthDownload); }
        }

        public double ContentLengthUpload
        {
            get { return getDoubleInfo(CurlInfo.ContentLengthUpload); }
        }

        public double RedirectTime
        {
            get { return getDoubleInfo(CurlInfo.RedirectTime); }
        }

        public CurlSlist SslEngines
        {
            get { return getSlistInfo(CurlInfo.SslEngines); }
        }

        public CurlCode LastErrorCode { get; private set; }

        /// <summary>
        ///     Cleanup unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void resetPrivateVariables()
        {
            Private = null;

            _pfCurlWrite = null;
            _writeData = null;

            _pfCurlRead = null;
            _readData = null;

            _pfCurlProgress = null;
            _progressData = null;

            _pfCurlDebug = null;
            _debugData = null;

            _pfCurlHeader = null;
            _headerData = null;

            _pfCurlSslContext = null;
            _sslContextData = null;

            _pfCurlIoctl = null;
            _ioctlData = null;
        }

        private CurlCode setMultiPartFormObject()
        {
            ensureHandle();
            var retCode = _httpMultiPartForm == null
                ? CurlCode.BadFunctionArgument
                : NativeMethods.curl_easy_setopt(_pCurl, CurlOption.HttpPost, _httpMultiPartForm.GetHandle());
            setLastError(retCode, CurlOption.HttpPost);
            return retCode;
        }

        private CurlCode setSlistObject(CurlOption option, CurlSlist slist)
        {
            ensureHandle();
            var retCode = NativeMethods.curl_easy_setopt(_pCurl, option, slist == null ? IntPtr.Zero : slist.Handle);
            setLastError(retCode, option);
            return retCode;
        }

        private CurlCode setShareObject()
        {
            ensureHandle();
            var retCode = _curlShare == null
                ? CurlCode.BadFunctionArgument
                : NativeMethods.curl_easy_setopt(_pCurl, CurlOption.Share, _curlShare.GetHandle());
            setLastError(retCode, CurlOption.Share);
            return retCode;
        }

        /// <summary>
        ///     Handles return code from all calls to 'curl_easy_xxx' functions.
        /// </summary>
        private void setLastError(CurlCode code, CurlOption opt)
        {
            if ((LastErrorCode == CurlCode.Ok) && (code != CurlCode.Ok))
            {
                LastErrorCode = code;
                LastErrorDescription = string.Format("Error: {0} setting option {1}", StrError(code), opt);
            }
        }

        /// <summary>
        ///     Handles return code from all calls to 'curl_easy_xxx' functions.
        /// </summary>
        private void setLastError(CurlCode code, CurlInfo info)
        {
            if ((LastErrorCode == CurlCode.Ok) && (code != CurlCode.Ok))
            {
                LastErrorCode = code;
                LastErrorDescription = string.Format("Error: {0} getting info {1}", StrError(code), info);
            }
        }

        /// <summary>
        ///     Destructor
        /// </summary>
        ~CurlEasy()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            lock (this)
            {
                if (disposing)
                {
                    // free managed resources
                    //if (managedResource != null)
                    //{
                    // managedResource.Dispose();
                    // managedResource = null;
                    //}
                }

                // free native resources if there are any.
                if (_pCurl != IntPtr.Zero)
                {
#if USE_LIBCURLSHIM
                    NativeMethods.curl_shim_cleanup_delegates(_ptrThis);
                    NativeMethods.curl_shim_free_strings(_pMyStrings);
#else
                    freeHandle(ref _curlWriteData);
                    freeHandle(ref _curlReadData);
                    freeHandle(ref _curlDebugData);
                    freeHandle(ref _curlProgressData);
                    freeHandle(ref _curlHeaderData);
                    freeHandle(ref _curlIoctlData);
                    freeHandle(ref _curlSslCtxData);
#endif
                    NativeMethods.curl_easy_cleanup(_pCurl);

                    _hThis.Free();
                    _pCurl = IntPtr.Zero;
                }
            }
        }

        private void ensureHandle()
        {
            if (_pCurl == IntPtr.Zero)
                throw new NullReferenceException("No internal easy handle");
        }

        internal IntPtr Handle
        {
            get
            {
                ensureHandle();
                return _pCurl;
            }
        }

        /// <summary>
        ///     Reset the internal cURL handle.
        /// </summary>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public void Reset()
        {
            ensureHandle();
            NativeMethods.curl_easy_reset(_pCurl);
        }

        private void setBoolOption(CurlOption option, ref bool field, bool value)
        {
            ensureHandle();
            field = value;
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, (IntPtr) Convert.ToInt32(value)), option);
        }

        private void setIntOption(CurlOption option, ref int field, int value)
        {
            ensureHandle();
            field = value;
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, (IntPtr) value), option);
        }

        private void setLongOption(CurlOption option, ref long field, long value)
        {
            ensureHandle();
            field = value;
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, (IntPtr) value), option);
        }

        private void setStringOption(CurlOption option, out string field, string value)
        {
            // all string options are copied by the library, the only exception to this rule is PostFields option
            if (option == CurlOption.PostFields)
                option = CurlOption.CopyPostFields;
            if ((option == CurlOption.CopyPostFields) && (PostFieldSize == 0))
                PostFieldSize = System.Text.Encoding.UTF8.GetByteCount(value);

            setStringOption(option, value);
            field = value;
        }

        private void setStringOption(CurlOption option, string value)
        {
            ensureHandle();
            if (string.IsNullOrEmpty(value))
            {
                setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, IntPtr.Zero), option);
            }
            else
            {
#if USE_LIBCURLSHIM
                var pCurlStr = NativeMethods.curl_shim_add_string(_pMyStrings, value);
                if (pCurlStr != IntPtr.Zero)
                    setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, pCurlStr), option);
#else
                // convert the string to a null-terminated one
                var buffer = System.Text.Encoding.UTF8.GetBytes(value + "\0");
                unsafe
                {
                    fixed (byte* bufPtr = &buffer[0])
                    {
                        setLastError(NativeMethods.curl_easy_setopt(_pCurl, option, buffer), option);
                    }
                }
#endif
            }
        }

        /// <summary>
        ///     Set options for this object. See the <c>EasyGet</c> sample for
        ///     basic usage.
        /// </summary>
        /// <param name="option">This should be a valid <see cref="CurlOption" />.</param>
        /// <param name="parameter">
        ///     This should be a parameter of a varying
        ///     type based on the value of the <c>option</c> parameter.
        /// </param>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        /// <returns>
        ///     A <see cref="CurlCode" />, typically obtained from
        ///     <c>cURL</c> internally, but sometimes a
        ///     <see cref="CurlCode.BadFunctionArgument" />
        ///     will be returned if the type of value of <c>parameter</c> is invalid.
        /// </returns>
        public CurlCode SetOpt(CurlOption option, object parameter)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;

            // numeric cases
            if ((int) option < CURLOPTTYPE_OBJECTPOINT)
            {
                var i = 0;
                if ((option == CurlOption.DnsUseGlobalCache) || (option == CurlOption.SourcePort))
                    return CurlCode.BadFunctionArgument;

                if (option == CurlOption.TimeValue)
                {
                    // unboxing may throw class cast exception
                    var d = (DateTime) parameter;
                    var startTime = new DateTime(1970, 1, 1);
                    var currTime = new TimeSpan(DateTime.Now.Ticks - startTime.Ticks);
                    i = Convert.ToInt32(currTime.TotalSeconds);
                }
                else
                    i = Convert.ToInt32(parameter);

                retCode = NativeMethods.curl_easy_setopt(_pCurl, option, (IntPtr) i);
            }

            // object cases: the majority
            else if ((int) option < CURLOPTTYPE_FUNCTIONPOINT)
            {
                return setObjectOptions(option, parameter);
            }
            // FUNCTIONPOINT args, for delegates
            else if ((int) option < CURLOPTTYPE_OFF_T)
            {
                return setFunctionOptions(option, parameter);
            }
            // otherwise, it's one of those 64-bit off_t guys!
            else
            {
                var i = Convert.ToInt64(parameter);
                retCode = NativeMethods.curl_easy_setopt(_pCurl, option, i);
            }

            return retCode;
        }

        private CurlCode setObjectOptions(CurlOption option, object parameter)
        {
            var retCode = CurlCode.Ok;
            switch (option)
            {
                // various data items
                case CurlOption.Private:
                    Private = parameter;
                    break;
                case CurlOption.WriteData:
                    _writeData = parameter;
                    break;
                case CurlOption.ReadData:
                    _readData = parameter;
                    break;
                case CurlOption.ProgressData:
                    _progressData = parameter;
                    break;
                case CurlOption.DebugData:
                    _debugData = parameter;
                    break;
                case CurlOption.HeaderData:
                    _headerData = parameter;
                    break;
                case CurlOption.SslCtxData:
                    _sslContextData = parameter;
                    break;
                case CurlOption.IoctlData:
                    _ioctlData = parameter;
                    break;

                // items that can't be set externally or
                // obsolete items
                case CurlOption.ErrorBuffer:
                case CurlOption.Stderr:
                case CurlOption.SourceHost:
                case CurlOption.SourcePath:
                case CurlOption.PasvHost:
                    return CurlCode.BadFunctionArgument;

                // singular case for share
                case CurlOption.Share:
                {
                    _curlShare = parameter as CurlShare;
                    retCode = setShareObject();
                    break;
                }

                // multipart HTTP post
                case CurlOption.HttpPost:
                {
                    _httpMultiPartForm = parameter as CurlHttpMultiPartForm;
                    retCode = setMultiPartFormObject();
                    break;
                }

                // items curl wants as a curl_slist
                case CurlOption.HttpHeader:
                case CurlOption.Prequote:
                case CurlOption.Quote:
                case CurlOption.Postquote:
                case CurlOption.SourceQuote:
                case CurlOption.TelnetOptions:
                case CurlOption.Http200Aliases:
                {
                    var slist = parameter as CurlSlist;
                    retCode = setSlistObject(option, slist);
                    break;
                }

                // string items
                default:
                {
                    var s = parameter as string;
                    setStringOption(option, s);
                    retCode = LastErrorCode;
                    break;
                }
            }
            return retCode;
        }

#if !USE_LIBCURLSHIM

        #region Object pinning support

        /// <summary>
        ///     Free the pinned object
        /// </summary>
        /// <param name="handle"></param>
        private void freeHandle(ref IntPtr handle)
        {
            if (handle == IntPtr.Zero)
                return;
            var handleCallback = GCHandle.FromIntPtr(handle);
            handleCallback.Free();
            handle = IntPtr.Zero;
        }

        /// <summary>
        ///     Pin the object in memory so the C function can find it
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static IntPtr getHandle(object obj)
        {
            if (obj == null)
                return IntPtr.Zero;
            return GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Pinned));
        }

        /// <summary>
        ///     Returns the object passed to a Set...Data function.
        ///     Cast back to the original object.
        /// </summary>
        /// <param name="userdata"></param>
        /// <returns></returns>
        private static object getObject(IntPtr userdata)
        {
            if (userdata == IntPtr.Zero)
                return null;
            var handle = GCHandle.FromIntPtr(userdata);
            return handle.Target;
        }

        #endregion

#endif

        private CurlCode setFunctionOptions(CurlOption option, object pfn)
        {
            switch (option)
            {
                case CurlOption.WriteFunction:
                {
                    var wf = pfn as CurlWriteCallback;
                    if (wf == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlWrite = wf;
                    break;
                }

                case CurlOption.ReadFunction:
                {
                    var rf = pfn as CurlReadCallback;
                    if (rf == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlRead = rf;
                    break;
                }

                case CurlOption.ProgressFunction:
                {
                    var pf = pfn as CurlProgressCallback;
                    if (pf == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlProgress = pf;
                    break;
                }

                case CurlOption.DebugFunction:
                {
                    var pd = pfn as CurlDebugCallback;
                    if (pd == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlDebug = pd;
                    break;
                }

                case CurlOption.HeaderFunction:
                {
                    var hf = pfn as CurlHeaderCallback;
                    if (hf == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlHeader = hf;
                    break;
                }

                case CurlOption.SslCtxFunction:
                {
                    var sf = pfn as CurlSslContextCallback;
                    if (sf == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlSslContext = sf;
                    break;
                }

                case CurlOption.IoctlFunction:
                {
                    var iof = pfn as CurlIoctlCallback;
                    if (iof == null)
                        return CurlCode.BadFunctionArgument;
                    _pfCurlIoctl = iof;
                    break;
                }

                default:
                    return CurlCode.BadFunctionArgument;
            }
            return CurlCode.Ok;
        }

        /// <summary>
        ///     Perform a transfer.
        /// </summary>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_perform()</c>.
        /// </returns>
        public CurlCode Perform()
        {
            ensureHandle();

            var nativeRet = NativeMethods.curl_easy_perform(_pCurl);
#if !USE_LIBCURLSHIM
            freeHandle(ref _curlWriteData);
#endif

            return nativeRet;
        }

        /// <summary>
        ///     Clone an CurlEasy object.
        /// </summary>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        /// <returns>A cloned <c>CurlEasy</c> object.</returns>
        public CurlEasy Clone()
        {
            return new CurlEasy(this);
        }

        /// <summary>
        ///     URL encode a String.
        /// </summary>
        /// <param name="url">The string to URL encode.</param>
        /// <param name="length">
        ///     Input string length;
        ///     use 0 for cURL to determine.
        /// </param>
        /// <returns>A new URL encoded string.</returns>
        /// <exception cref="NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public string Escape(string url)
        {
            ensureHandle();

            var length = System.Text.Encoding.ASCII.GetBytes(url).Length;
            var p = NativeMethods.curl_easy_escape(_pCurl, url, length);
            var s = Marshal.PtrToStringAnsi(p);
            NativeMethods.curl_free(p);
            return s;
        }

        /// <summary>
        ///     URL decode a String.
        /// </summary>
        /// <param name="url">The string to URL decode.</param>
        /// <param name="length">
        ///     Input string length;
        ///     use 0 for cURL to determine.
        /// </param>
        /// <returns>A new URL decoded string.</returns>
        /// <exception cref="NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public string Unescape(string url)
        {
            ensureHandle();
            
            var length = System.Text.Encoding.ASCII.GetBytes(url).Length;
            var p = NativeMethods.curl_easy_unescape(_pCurl, url, length, out int outLength);
            var s = Marshal.PtrToStringAnsi(p, outLength);
            NativeMethods.curl_free(p);
            return s;
        }

        /// <summary>
        ///     Get a string description of an error code.
        /// </summary>
        /// <param name="code">Error code.</param>
        /// <returns>String description of the error code.</returns>
        public string StrError(CurlCode code)
        {
            return Marshal.PtrToStringAnsi(NativeMethods.curl_easy_strerror(code));
        }

        /// <summary>
        ///     Extract information from a cURL handle.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration.
        /// </param>
        /// <param name="objInfo">
        ///     Reference to an object into which the
        ///     value specified by <c>info</c> is written.
        /// </param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref object objInfo)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;
            var ptr = IntPtr.Zero;

            if ((int) info < CURLINFO_STRING)
                return CurlCode.BadFunctionArgument;

            // trickery for filetime
            if (info == CurlInfo.Filetime)
                return CurlCode.BadFunctionArgument;

            // private data
            if (info == CurlInfo.Private)
            {
                objInfo = Private;
                return retCode;
            }

            // string case
            if ((int) info < CURLINFO_LONG)
            {
                retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
                if (retCode == CurlCode.Ok)
                    objInfo = Marshal.PtrToStringAnsi(ptr);
                return retCode;
            }

            // int or double: return problem
            return CurlCode.BadFunctionArgument;
        }

        private string getStringInfo(CurlInfo info)
        {
            ensureHandle();
            var ptr = IntPtr.Zero;
            var retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            setLastError(retCode, info);
            return retCode == CurlCode.Ok ? Marshal.PtrToStringAnsi(ptr) : string.Empty;
        }

        private int getIntInfo(CurlInfo info)
        {
            ensureHandle();
            // ensure it's an integral type
            if (((int) info < CURLINFO_LONG) || ((int) info >= CURLINFO_DOUBLE))
            {
                setLastError(CurlCode.BadFunctionArgument, info);
                return -1;
            }

            var ptr = IntPtr.Zero;
            var retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            setLastError(retCode, info);
            return retCode == CurlCode.Ok ? (int) ptr : -1;
        }

        private DateTime getDateTimeInfo(CurlInfo info)
        {
            var dt = new DateTime();
            setLastError(GetInfo(info, ref dt), info);
            return dt;
        }

        private double getDoubleInfo(CurlInfo info)
        {
            double val = 0;
            setLastError(GetInfo(info, ref val), info);
            return val;
        }

        private CurlSlist getSlistInfo(CurlInfo info)
        {
            CurlSlist val = null;
            setLastError(GetInfo(info, ref val), info);
            return val;
        }

        /// <summary>
        ///     Extract <c>CurlSlist</c> information from an <c>CurlEasy</c> object.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration. In this case, it must
        ///     specifically be one of the members that obtains an <c>CurlSlist</c>.
        /// </param>
        /// <param name="slist">Reference to an <c>CurlSlist</c> value.</param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref CurlSlist slist)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;
            IntPtr ptr = IntPtr.Zero, ptrs = IntPtr.Zero;

            if ((int) info < CURLINFO_SLIST)
                return CurlCode.BadFunctionArgument;
            retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            if (retCode != CurlCode.Ok)
                return retCode;
            slist = new CurlSlist();
            while (ptr != IntPtr.Zero)
            {
#if USE_LIBCURLSHIM
                ptr = NativeMethods.curl_shim_get_string_from_slist(ptr, ref ptrs);
                slist.Append(Marshal.PtrToStringAnsi(ptrs));
#else
                //TODO: implement
                throw new NotImplementedException();
#endif
            }
            return retCode;
        }

        /// <summary>
        ///     Extract <c>string</c> information from an <c>CurlEasy</c> object.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration. In this case, it must
        ///     specifically be one of the members that obtains a <c>string</c>.
        /// </param>
        /// <param name="strVal">Reference to an <c>string</c> value.</param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref string strVal)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;
            var ptr = IntPtr.Zero;

            if (((int) info < CURLINFO_STRING) || ((int) info >= CURLINFO_LONG))
                return CurlCode.BadFunctionArgument;
            retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            if (retCode == CurlCode.Ok)
                strVal = Marshal.PtrToStringAnsi(ptr);
            return retCode;
        }

        /// <summary>
        ///     Extract <c>int</c> information from an <c>CurlEasy</c> object.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration. In this case, it must
        ///     specifically be one of the members that obtains a <c>double</c>.
        /// </param>
        /// <param name="dblVal">Reference to an <c>double</c> value.</param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref double dblVal)
        {
            ensureHandle();

            // ensure it's an integral type
            if ((int) info < CURLINFO_DOUBLE)
                return CurlCode.BadFunctionArgument;

            return NativeMethods.curl_easy_getinfo(_pCurl, info, ref dblVal);
        }

        /// <summary>
        ///     Extract <c>int</c> information from an <c>CurlEasy</c> object.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration. In this case, it must
        ///     specifically be one of the members that obtains an <c>int</c>.
        /// </param>
        /// <param name="intVal">Reference to an <c>int</c> value.</param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref int intVal)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;
            var ptr = IntPtr.Zero;

            // ensure it's an integral type
            if (((int) info < CURLINFO_LONG) || ((int) info >= CURLINFO_DOUBLE))
                return CurlCode.BadFunctionArgument;

            retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            if (retCode == CurlCode.Ok)
                intVal = (int) ptr;
            return retCode;
        }

        /// <summary>
        ///     Extract <c>DateTime</c> information from an <c>CurlEasy</c> object.
        /// </summary>
        /// <param name="info">
        ///     One of the values in the
        ///     <see cref="CurlInfo" /> enumeration. In this case, it must
        ///     specifically be <see cref="CurlInfo.Filetime" />.
        /// </param>
        /// <param name="dt">Reference to a <c>DateTime</c> value.</param>
        /// <returns>
        ///     The <see cref="CurlCode" /> obtained from the internal
        ///     call to <c>curl_easy_getinfo()</c>.
        /// </returns>
        /// <exception cref="System.NullReferenceException">
        ///     This is thrown if
        ///     the native <c>CURL*</c> handle wasn't created successfully.
        /// </exception>
        public CurlCode GetInfo(CurlInfo info, ref DateTime dt)
        {
            ensureHandle();
            var retCode = CurlCode.Ok;
            var ptr = IntPtr.Zero;

            if (info != CurlInfo.Filetime)
                return CurlCode.BadFunctionArgument;

            retCode = NativeMethods.curl_easy_getinfo(_pCurl, info, ref ptr);
            if (retCode == CurlCode.Ok)
                if ((int) ptr < 0)
                    dt = new DateTime(0);
            return retCode;
        }

        // install the fuctions that will be called from libcurlshim
        private void installDelegates()
        {
            ensureHandle();
            _hThis = GCHandle.Alloc(this);
#if USE_LIBCURLSHIM
            _pcbWrite = _shimWriteCallback;
            _pcbRead = _shimReadCallback;
            _pcbProgress = _shimProgressCallback;
            _pcbDebug = _shimDebugCallback;
            _pcbHeader = _shimHeaderCallback;
            _pcbSslCtx = _shimSslCtxCallback;
            _pcbIoctl = _shimIoctlCallback;
            _ptrThis = (IntPtr)_hThis;
            NativeMethods.curl_shim_install_delegates(
                _pCurl, _ptrThis,
                _pcbWrite, _pcbRead, _pcbProgress,
                _pcbDebug, _pcbHeader, _pcbSslCtx,
                _pcbIoctl);
#else
            _pcbWrite = _curlWriteCallback;
            _pcbRead = _curlReadCallback;
            _pcbProgress = _curlProgressCallback;
            _pcbDebug = _curlDebugCallback;
            _pcbHeader = _curlHeaderCallback;
            _pcbSslCtx = _curlSslCtxCallback;
            _pcbIoctl = _curlIoctlCallback;

            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.WriteFunction, _pcbWrite),
                CurlOption.WriteFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.ReadFunction, _pcbRead),
                CurlOption.ReadFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.ProgressFunction, _pcbProgress),
                CurlOption.ProgressFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.HeaderFunction, _pcbHeader),
                CurlOption.HeaderFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.DebugFunction, _pcbDebug),
                CurlOption.DebugFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.SslCtxFunction, _pcbSslCtx),
                CurlOption.SslCtxFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.IoctlFunction, _pcbIoctl),
                CurlOption.IoctlFunction);
            setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.NoProgress, (IntPtr) 0),
                CurlOption.NoProgress);

            setWriteData(null);
            setReadData(null);
            setHeaderData(null);
            setProgressData(null);
            setDebugData(null);
            setSslCtxData(null);
            setIoctlData(null);
#endif
        }

#if USE_LIBCURLSHIM
        // called by libcurlshim
        private static int _shimWriteCallback(IntPtr buf, int sz, int nmemb, IntPtr parm)
        {
            var bytes = sz*nmemb;
            var b = new byte[bytes];
            for (var i = 0; i < bytes; i++)
                b[i] = Marshal.ReadByte(buf, i);
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy == null)
                return 0;
            if (curlEasy._pfCurlWrite == null)
                return bytes; // keep going
            return curlEasy._pfCurlWrite(b, sz, nmemb, curlEasy._writeData);
        }

        // called by libcurlshim
        private static int _shimReadCallback(IntPtr buf, int sz, int nmemb, IntPtr parm)
        {
            var bytes = sz*nmemb;
            var b = new byte[bytes];
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy?._pfCurlRead == null)
                return 0;
            var nRead = curlEasy._pfCurlRead(b, sz, nmemb, curlEasy._readData);
            if (nRead > 0)
            {
                for (var i = 0; i < nRead; i++)
                    Marshal.WriteByte(buf, i, b[i]);
            }
            return nRead;
        }

        // called by libcurlshim
        private static int _shimProgressCallback(IntPtr parm, double dlTotal, double dlNow, double ulTotal, double ulNow)
        {
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy?._pfCurlProgress == null)
                return 0;
            var nprog = curlEasy._pfCurlProgress(curlEasy._progressData, dlTotal, dlNow, ulTotal, ulNow);
            return nprog;
        }

        // called by libcurlshim
        private static int _shimDebugCallback(CurlInfoType infoType, IntPtr msgBuf, int msgBufSize, IntPtr parm)
        {
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy?._pfCurlDebug == null)
                return 0;
            var message = Marshal.PtrToStringAnsi(msgBuf, msgBufSize);
            curlEasy._pfCurlDebug(infoType, message, msgBufSize, curlEasy._debugData);
            return 0;
        }

        // called by libcurlshim
        private static int _shimHeaderCallback(IntPtr buf, int sz, int nmemb, IntPtr parm)
        {
            var bytes = sz*nmemb;
            var b = new byte[bytes];
            for (var i = 0; i < bytes; i++)
                b[i] = Marshal.ReadByte(buf, i);
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy == null)
                return 0;
            if (curlEasy._pfCurlHeader == null)
                return bytes; // keep going
            return curlEasy._pfCurlHeader(b, sz, nmemb, curlEasy._headerData);
        }

        // called by libcurlshim
        private static int _shimSslCtxCallback(IntPtr ctx, IntPtr parm)
        {
            const int OK_RETURN = (int) CurlCode.Ok;
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            if (curlEasy == null)
                return OK_RETURN;
            if (curlEasy._pfCurlSslContext == null)
                return OK_RETURN; // keep going
            var context = new CurlSslContext(ctx);
            return (int) curlEasy._pfCurlSslContext(context, curlEasy._sslContextData);
        }

        // called by libcurlshim
        private static CurlIoError _shimIoctlCallback(CurlIoCommand cmd, IntPtr parm)
        {
            var gch = (GCHandle) parm;
            var curlEasy = (CurlEasy) gch.Target;
            // let's require all of these to be non-null
            if (curlEasy == null || curlEasy._pfCurlIoctl == null || curlEasy._ioctlData == null)
            {
                return CurlIoError.UnknownCommand;
            }
            return curlEasy._pfCurlIoctl(cmd, curlEasy._ioctlData);
        }
#else
        // called by libcurl
        private int _curlWriteCallback(IntPtr pBuffer, int sz, int nmemb, IntPtr pUserData)
        {
            var bytes = sz*nmemb;
            if (_pfCurlWrite == null) return bytes; // keep going
            var buf = new byte[bytes];
            Marshal.Copy(pBuffer, buf, 0, bytes);
            var userdata = getObject(pUserData);
            return _pfCurlWrite(buf, sz, nmemb, userdata);
        }

        private int _curlReadCallback(IntPtr pBuffer, int sz, int nmemb, IntPtr pUserData)
        {
            var bytes = sz*nmemb;
            if (_pfCurlRead == null) return bytes; // keep going
            var userdata = getObject(pUserData);
            var buffer = new byte[bytes];
            var nRead = _pfCurlRead(buffer, sz, nmemb, userdata);
            if (nRead > 0)
                Marshal.Copy(buffer, 0, pBuffer, nRead);
            return nRead;
        }

        private int _curlProgressCallback(IntPtr ptrUserdata, double dlTotal, double dlNow, double ulTotal, double ulNow)
        {
            if (_pfCurlProgress != null)
            {
                var userdata = getObject(ptrUserdata);
                return _pfCurlProgress(userdata, dlTotal, dlNow, ulTotal, ulNow);
            }
            return 0;
        }

        private int _curlDebugCallback(IntPtr pCurl, CurlInfoType infoType, string message, int size, IntPtr pUserData)
        {
            if (_pfCurlDebug != null)
            {
                var userdata = getObject(pUserData);
                _pfCurlDebug(infoType, message, size, userdata);
            }
            return 0;
        }

        private int _curlHeaderCallback(IntPtr pBuffer, int sz, int nmemb, IntPtr pUserdata)
        {
            var bytes = sz*nmemb;
            if (_pfCurlHeader != null)
            {
                var buf = new byte[bytes];
                Marshal.Copy(pBuffer, buf, 0, bytes);
                var userdata = getObject(pUserdata);
                return _pfCurlHeader(buf, sz, nmemb, userdata);
            }
            return bytes; // keep going
        }

        private int _curlSslCtxCallback(IntPtr ctx, IntPtr parm)
        {
            if (_pfCurlSslContext == null)
                return (int) CurlCode.Ok; // keep going
            var context = new CurlSslContext(ctx);
            return (int) _pfCurlSslContext(context, _sslContextData);
        }

        private CurlIoError _curlIoctlCallback(CurlIoCommand cmd, IntPtr parm)
        {
            if ((_pfCurlIoctl == null) || (_ioctlData == null))
                return CurlIoError.UnknownCommand;
            return _pfCurlIoctl(cmd, _ioctlData);
        }
#endif
    }
}