/*********************************************************************
 *
 * $Id: YFunction.cs 70668 2025-12-09 10:29:44Z seb $
 *
 * YFunction Class (virtual class, used internally)
 *
 * - - - - - - - - - License information: - - - - - - - - -
 *
 *  Copyright (C) 2011 and beyond by Yoctopuce Sarl, Switzerland.
 *
 *  Yoctopuce Sarl (hereafter Licensor) grants to you a perpetual
 *  non-exclusive license to use, modify, copy and integrate this
 *  file into your software for the sole purpose of interfacing
 *  with Yoctopuce products.
 *
 *  You may reproduce and distribute copies of this file in
 *  source or object form, as long as the sole purpose of this
 *  code is to interface with Yoctopuce products. You must retain
 *  this notice in the distributed source file.
 *
 *  You should refer to Yoctopuce General Terms and Conditions
 *  for additional information regarding your rights and
 *  obligations.
 *
 *  THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 *  WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 *  WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS
 *  FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO
 *  EVENT SHALL LICENSOR BE LIABLE FOR ANY INCIDENTAL, SPECIAL,
 *  INDIRECT OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA,
 *  COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR
 *  SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT
 *  LIMITED TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR
 *  CONTRIBUTION, OR OTHER SIMILAR COSTS, WHETHER ASSERTED ON THE
 *  BASIS OF CONTRACT, TORT (INCLUDING NEGLIGENCE), BREACH OF
 *  WARRANTY, OR OTHERWISE.
 *
 *********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;


namespace com.yoctopuce.YoctoAPI
{
    //--- (generated code: YFunction class start)
/**
 * <summary>
 *   YFunction Class: Common function interface
 * <para>
 *   This is the parent class for all public objects representing device functions documented in
 *   the high-level programming API. This abstract class does all the real job, but without
 *   knowledge of the specific function attributes.
 * </para>
 * <para>
 *   Instantiating a child class of YFunction does not cause any communication.
 *   The instance simply keeps track of its function identifier, and will dynamically bind
 *   to a matching device at the time it is really being used to read or set an attribute.
 *   In order to allow true hot-plug replacement of one device by another, the binding stay
 *   dynamic through the life of the object.
 * </para>
 * <para>
 *   The YFunction class implements a generic high-level cache for the attribute values of
 *   the specified function, pre-parsed from the REST API string.
 * </para>
 * </summary>
 */
public class YFunction
{
//--- (end of generated code: YFunction class start)

        public const string FUNCTIONDESCRIPTOR_INVALID = "!INVALID!";
        protected internal readonly YAPIContext _yapi;
        protected internal readonly string _className;
        protected internal readonly string _func;
        protected internal int _lastErrorType;
        protected internal string _lastErrorMsg;
        protected internal object _userData;

        protected internal readonly Dictionary<string, YDataStream> _dataStreams;

        //--- (generated code: YFunction definitions)
    /**
     * <summary>
     *   invalid logicalName value
     * </summary>
     */
    public const  string LOGICALNAME_INVALID = YAPI.INVALID_STRING;
    /**
     * <summary>
     *   invalid advertisedValue value
     * </summary>
     */
    public const  string ADVERTISEDVALUE_INVALID = YAPI.INVALID_STRING;
    protected string _logicalName = LOGICALNAME_INVALID;
    protected string _advertisedValue = ADVERTISEDVALUE_INVALID;
    protected ValueCallback _valueCallbackFunction = null;
    protected ulong _cacheExpiration = 0;
    protected string _serial;
    protected string _funId;
    protected string _hwId;

    public delegate Task ValueCallback(YFunction func, string value);
    public delegate Task TimedReportCallback(YFunction func, YMeasure measure);
    //--- (end of generated code: YFunction definitions)


        protected internal YFunction(YAPIContext yctx, string func, string classname)
        {
            _yapi = yctx;
            _className = classname;
            _func = func;
            _lastErrorType = YAPI.SUCCESS;
            _lastErrorMsg = "";
            _userData = null;
            _dataStreams = new Dictionary<string, YDataStream>();
            //--- (generated code: YFunction attributes initialization)
        //--- (end of generated code: YFunction attributes initialization)
        }

        protected internal YFunction(YAPIContext yctx, string func) : this(yctx, func, "Function")
        {
        }

        public YFunction(string func) : this(YAPI.imm_GetYCtx(), func, "Function")
        {
        }

        public int _throw(int errType, string errMsg)
        {
            _lastErrorType = errType;
            _lastErrorMsg = errMsg;
            if (!(_yapi._exceptionsDisabled)) {
                throw new YAPI_Exception(errType, "YoctoApi error : " + errMsg);
            }
            return errType;
        }


        protected internal static YFunction _FindFromCacheInContext(YAPIContext yctx, string className, string func)
        {
            return yctx._yHash.imm_getFunction(className, func);
        }

        protected internal static YFunction _FindFromCache(string className, string func)
        {
            YAPIContext ctx = YAPI.imm_GetYCtx();
            return ctx._yHash.imm_getFunction(className, func);
        }


        protected internal static void _AddToCache(string className, string func, YFunction obj)
        {
            obj._yapi._yHash.imm_setFunction(className, func, obj);
        }

        protected internal static async Task _UpdateValueCallbackList(YFunction func, bool add)
        {
            await func._yapi._UpdateValueCallbackList(func, add);
        }

        protected internal static async Task _UpdateTimedReportCallbackList(YFunction func, bool add)
        {
            await func._yapi._UpdateTimedReportCallbackList(func, add);
        }


        private async Task<bool> isReadOnly_internal()
        {
            try {
                YDevice dev = await getYDevice();
                return dev.Hub.isReadOnly();
            } catch (YAPI_Exception) {
                return true;
            }
        }


        //--- (generated code: YFunction implementation)
#pragma warning disable 1998
    internal virtual void imm_parseAttr(YJSONObject json_val)
    {
        if (json_val.has("logicalName")) {
            _logicalName = json_val.getString("logicalName");
        }
        if (json_val.has("advertisedValue")) {
            _advertisedValue = json_val.getString("advertisedValue");
        }
    }

    /**
     * <summary>
     *   Returns the logical name of the function.
     * <para>
     * </para>
     * <para>
     * </para>
     * </summary>
     * <returns>
     *   a string corresponding to the logical name of the function
     * </returns>
     * <para>
     *   On failure, throws an exception or returns <c>YFunction.LOGICALNAME_INVALID</c>.
     * </para>
     */
    public async Task<string> get_logicalName()
    {
        string res;
        if (_cacheExpiration <= YAPIContext.GetTickCount()) {
            if (await this.load(await _yapi.GetCacheValidity()) != YAPI.SUCCESS) {
                return LOGICALNAME_INVALID;
            }
        }
        res = _logicalName;
        return res;
    }


    /**
     * <summary>
     *   Changes the logical name of the function.
     * <para>
     *   You can use <c>yCheckLogicalName()</c>
     *   prior to this call to make sure that your parameter is valid.
     *   Remember to call the <c>saveToFlash()</c> method of the module if the
     *   modification must be kept.
     * </para>
     * <para>
     * </para>
     * </summary>
     * <param name="newval">
     *   a string corresponding to the logical name of the function
     * </param>
     * <para>
     * </para>
     * <returns>
     *   <c>YAPI.SUCCESS</c> if the call succeeds.
     * </returns>
     * <para>
     *   On failure, throws an exception or returns a negative error code.
     * </para>
     */
    public async Task<int> set_logicalName(string  newval)
    {
        string rest_val;
        if (!YAPI.CheckLogicalName(newval))
            _throw(YAPI.INVALID_ARGUMENT,"Invalid name :" + newval);
        rest_val = newval;
        await _setAttr("logicalName",rest_val);
        return YAPI.SUCCESS;
    }

    /**
     * <summary>
     *   Returns a short string representing the current state of the function.
     * <para>
     * </para>
     * <para>
     * </para>
     * </summary>
     * <returns>
     *   a string corresponding to a short string representing the current state of the function
     * </returns>
     * <para>
     *   On failure, throws an exception or returns <c>YFunction.ADVERTISEDVALUE_INVALID</c>.
     * </para>
     */
    public async Task<string> get_advertisedValue()
    {
        string res;
        if (_cacheExpiration <= YAPIContext.GetTickCount()) {
            if (await this.load(await _yapi.GetCacheValidity()) != YAPI.SUCCESS) {
                return ADVERTISEDVALUE_INVALID;
            }
        }
        res = _advertisedValue;
        return res;
    }


    public async Task<int> set_advertisedValue(string  newval)
    {
        string rest_val;
        rest_val = newval;
        await _setAttr("advertisedValue",rest_val);
        return YAPI.SUCCESS;
    }

    /**
     * <summary>
     *   Retrieves a function for a given identifier.
     * <para>
     *   The identifier can be specified using several formats:
     * </para>
     * <para>
     * </para>
     * <para>
     *   - FunctionLogicalName
     * </para>
     * <para>
     *   - ModuleSerialNumber.FunctionIdentifier
     * </para>
     * <para>
     *   - ModuleSerialNumber.FunctionLogicalName
     * </para>
     * <para>
     *   - ModuleLogicalName.FunctionIdentifier
     * </para>
     * <para>
     *   - ModuleLogicalName.FunctionLogicalName
     * </para>
     * <para>
     * </para>
     * <para>
     *   This function does not require that the function is online at the time
     *   it is invoked. The returned object is nevertheless valid.
     *   Use the method <c>YFunction.isOnline()</c> to test if the function is
     *   indeed online at a given time. In case of ambiguity when looking for
     *   a function by logical name, no error is notified: the first instance
     *   found is returned. The search is performed first by hardware name,
     *   then by logical name.
     * </para>
     * <para>
     *   If a call to this object's is_online() method returns FALSE although
     *   you are certain that the matching device is plugged, make sure that you did
     *   call registerHub() at application initialization time.
     * </para>
     * <para>
     * </para>
     * </summary>
     * <param name="func">
     *   a string that uniquely characterizes the function, for instance
     *   <c>MyDevice.</c>.
     * </param>
     * <returns>
     *   a <c>YFunction</c> object allowing you to drive the function.
     * </returns>
     */
    public static YFunction FindFunction(string func)
    {
        YFunction obj;
        obj = (YFunction) YFunction._FindFromCache("Function", func);
        if (obj == null) {
            obj = new YFunction(func);
            YFunction._AddToCache("Function", func, obj);
        }
        return obj;
    }

    /**
     * <summary>
     *   Retrieves a function for a given identifier in a YAPI context.
     * <para>
     *   The identifier can be specified using several formats:
     * </para>
     * <para>
     * </para>
     * <para>
     *   - FunctionLogicalName
     * </para>
     * <para>
     *   - ModuleSerialNumber.FunctionIdentifier
     * </para>
     * <para>
     *   - ModuleSerialNumber.FunctionLogicalName
     * </para>
     * <para>
     *   - ModuleLogicalName.FunctionIdentifier
     * </para>
     * <para>
     *   - ModuleLogicalName.FunctionLogicalName
     * </para>
     * <para>
     * </para>
     * <para>
     *   This function does not require that the function is online at the time
     *   it is invoked. The returned object is nevertheless valid.
     *   Use the method <c>YFunction.isOnline()</c> to test if the function is
     *   indeed online at a given time. In case of ambiguity when looking for
     *   a function by logical name, no error is notified: the first instance
     *   found is returned. The search is performed first by hardware name,
     *   then by logical name.
     * </para>
     * </summary>
     * <param name="yctx">
     *   a YAPI context
     * </param>
     * <param name="func">
     *   a string that uniquely characterizes the function, for instance
     *   <c>MyDevice.</c>.
     * </param>
     * <returns>
     *   a <c>YFunction</c> object allowing you to drive the function.
     * </returns>
     */
    public static YFunction FindFunctionInContext(YAPIContext yctx,string func)
    {
        YFunction obj;
        obj = (YFunction) YFunction._FindFromCacheInContext(yctx, "Function", func);
        if (obj == null) {
            obj = new YFunction(yctx, func);
            YFunction._AddToCache("Function", func, obj);
        }
        return obj;
    }

    /**
     * <summary>
     *   Registers the callback function that is invoked on every change of advertised value.
     * <para>
     *   The callback is invoked only during the execution of <c>ySleep</c> or <c>yHandleEvents</c>.
     *   This provides control over the time when the callback is triggered. For good responsiveness, remember to call
     *   one of these two functions periodically. To unregister a callback, pass a null pointer as argument.
     * </para>
     * <para>
     * </para>
     * </summary>
     * <param name="callback">
     *   the callback function to call, or a null pointer. The callback function should take two
     *   arguments: the function object of which the value has changed, and the character string describing
     *   the new advertised value.
     * @noreturn
     * </param>
     */
    public virtual async Task<int> registerValueCallback(ValueCallback callback)
    {
        string val;
        if (callback != null) {
            await YFunction._UpdateValueCallbackList(this, true);
        } else {
            await YFunction._UpdateValueCallbackList(this, false);
        }
        _valueCallbackFunction = callback;
        // Immediately invoke value callback with current value
        if (callback != null && await this.isOnline()) {
            val = _advertisedValue;
            if (!(val == "")) {
                await this._invokeValueCallback(val);
            }
        }
        return 0;
    }

    public virtual async Task<int> _invokeValueCallback(string value)
    {
        if (_valueCallbackFunction != null) {
            await _valueCallbackFunction(this, value);
        } else {
        }
        return 0;
    }

    /**
     * <summary>
     *   Disables the propagation of every new advertised value to the parent hub.
     * <para>
     *   You can use this function to save bandwidth and CPU on computers with limited
     *   resources, or to prevent unwanted invocations of the HTTP callback.
     *   Remember to call the <c>saveToFlash()</c> method of the module if the
     *   modification must be kept.
     * </para>
     * </summary>
     * <returns>
     *   <c>YAPI.SUCCESS</c> when the call succeeds.
     * </returns>
     * <para>
     *   On failure, throws an exception or returns a negative error code.
     * </para>
     */
    public virtual async Task<int> muteValueCallbacks()
    {
        return await this.set_advertisedValue("SILENT");
    }

    /**
     * <summary>
     *   Re-enables the propagation of every new advertised value to the parent hub.
     * <para>
     *   This function reverts the effect of a previous call to <c>muteValueCallbacks()</c>.
     *   Remember to call the <c>saveToFlash()</c> method of the module if the
     *   modification must be kept.
     * </para>
     * </summary>
     * <returns>
     *   <c>YAPI.SUCCESS</c> when the call succeeds.
     * </returns>
     * <para>
     *   On failure, throws an exception or returns a negative error code.
     * </para>
     */
    public virtual async Task<int> unmuteValueCallbacks()
    {
        return await this.set_advertisedValue("");
    }

    /**
     * <summary>
     *   Returns the current value of a single function attribute, as a text string, as quickly as
     *   possible but without using the cached value.
     * <para>
     * </para>
     * <para>
     * </para>
     * </summary>
     * <param name="attrName">
     *   the name of the requested attribute
     * </param>
     * <returns>
     *   a string with the value of the the attribute
     * </returns>
     * <para>
     *   On failure, throws an exception or returns an empty string.
     * </para>
     */
    public virtual async Task<string> loadAttribute(string attrName)
    {
        string url;
        byte[] attrVal = new byte[0];
        url = "api/"+await this.get_functionId()+"/"+attrName;
        attrVal = await this._download(url);
        return YAPI.DefaultEncoding.GetString(attrVal);
    }

    //cannot be generated for UWP:
    //public virtual async Task<bool> isReadOnly_internal()
    /**
     * <summary>
     *   Indicates whether changes to the function are prohibited or allowed.
     * <para>
     *   Returns <c>true</c> if the function is blocked by an admin password
     *   or if the function is not available.
     * </para>
     * <para>
     * </para>
     * </summary>
     * <returns>
     *   <c>true</c> if the function is write-protected or not online.
     * </returns>
     */
    public virtual async Task<bool> isReadOnly()
    {
        return await isReadOnly_internal();
    }

    /**
     * <summary>
     *   Returns the serial number of the module, as set by the factory.
     * <para>
     * </para>
     * </summary>
     * <returns>
     *   a string corresponding to the serial number of the module, as set by the factory.
     * </returns>
     * <para>
     *   On failure, throws an exception or returns YFunction.SERIALNUMBER_INVALID.
     * </para>
     */
    public virtual async Task<string> get_serialNumber()
    {
        YModule m;
        m = await this.get_module();
        return await m.get_serialNumber();
    }

    public virtual async Task<int> _parserHelper()
    {
        return 0;
    }

    public virtual bool _is_valid_pass(string passwd)
    {
        string tmp;
        if ((passwd).Length > YAPI.HASH_BUF_SIZE) {
            tmp = "Password too long (max "+Convert.ToString(YAPI.HASH_BUF_SIZE)+" chars) :"+passwd;
            this._throw(YAPI.INVALID_ARGUMENT, tmp);
            return false;
        }
        if ((passwd).IndexOf("@") >=0) {
            this._throw(YAPI.INVALID_ARGUMENT, "Character @ is not allowed in password");
            return false;
        }
        if ((passwd).IndexOf("/") >=0) {
            this._throw(YAPI.INVALID_ARGUMENT, "Character / is not allowed in password");
            return false;
        }
        return true;
    }

    /**
     * <summary>
     *   c
     * <para>
     *   omment from .yc definition
     * </para>
     * </summary>
     */
    public YFunction nextFunction()
    {
        string next_hwid;
        try {
            string hwid = _yapi._yHash.imm_resolveHwID(_className, _func);
            next_hwid = _yapi._yHash.imm_getNextHardwareId(_className, hwid);
        } catch (YAPI_Exception) {
            next_hwid = null;
        }
        if(next_hwid == null) return null;
        return FindFunctionInContext(_yapi, next_hwid);
    }

    /**
     * <summary>
     *   c
     * <para>
     *   omment from .yc definition
     * </para>
     * </summary>
     */
    public static YFunction FirstFunction()
    {
        YAPIContext yctx = YAPI.imm_GetYCtx();
        string next_hwid = yctx._yHash.imm_getFirstHardwareId("Function");
        if (next_hwid == null)  return null;
        return FindFunctionInContext(yctx, next_hwid);
    }

    /**
     * <summary>
     *   c
     * <para>
     *   omment from .yc definition
     * </para>
     * </summary>
     */
    public static YFunction FirstFunctionInContext(YAPIContext yctx)
    {
        string next_hwid = yctx._yHash.imm_getFirstHardwareId("Function");
        if (next_hwid == null)  return null;
        return FindFunctionInContext(yctx, next_hwid);
    }

#pragma warning restore 1998
    //--- (end of generated code: YFunction implementation)

        /// <summary>
        ///  Returns a short text that describes unambiguously the instance of the function in the form
        /// TYPE(NAME)=SERIAL&#46;FUNCTIONID.
        /// More precisely,
        /// TYPE       is the type of the function,
        /// NAME       it the name used for the first access to the function,
        /// SERIAL     is the serial number of the module if the module is connected or "unresolved", and
        /// FUNCTIONID is  the hardware identifier of the function if the module is connected.
        /// For example, this method returns Relay(MyCustomName.relay1)=RELAYLO1-123456.relay1 if the
        /// module is already connected or Relay(BadCustomeName.relay1)=unresolved if the module has
        /// not yet been connected. This method does not trigger any USB or TCP transaction and can therefore be used in
        /// a debugger.
        /// </summary>
        /// <returns> a string that describes the function
        ///         (ex: Relay(MyCustomName.relay1)=RELAYLO1-123456.relay1) </returns>
        public virtual string describe()
        {
            try {
                string hwid = _yapi._yHash.imm_resolveHwID(_className, _func);
                return _className + "(" + _func + ")=" + hwid;
            } catch (YAPI_Exception) {
            }
            return _className + "(" + _func + ")=unresolved";
        }

#pragma warning disable 1998

        /// <summary>
        /// Returns the unique hardware identifier of the function in the form SERIAL.FUNCTIONID.
        /// The unique hardware identifier is composed of the device serial
        /// number and of the hardware identifier of the function (for example RELAYLO1-123456.relay1).
        /// </summary>
        /// <returns> a string that uniquely identifies the function (ex: RELAYLO1-123456.relay1)
        /// </returns>
        /// <exception cref="YAPI_Exception"> on error </exception>
        public virtual async Task<string> get_hardwareId()
        {
            //fixme: add fallback to isOnline to check that device is present
            return _yapi._yHash.imm_resolveHwID(_className, _func);
        }

        internal virtual string imm_get_hardwareId()
        {
            return _yapi._yHash.imm_resolveHwID(_className, _func);
        }


        /// <summary>
        /// Returns the hardware identifier of the function, without reference to the module. For example
        /// relay1
        /// </summary>
        /// <returns> a string that identifies the function (ex: relay1)
        /// </returns>
        /// <exception cref="YAPI_Exception"> on error </exception>
        public virtual async Task<string> get_functionId()
        {
            return _yapi._yHash.imm_resolveFuncId(_className, _func);
        }

        internal virtual string imm_get_functionId()
        {
            return _yapi._yHash.imm_resolveFuncId(_className, _func);
        }


        /// <summary>
        /// Returns a global identifier of the function in the format MODULE_NAME&#46;FUNCTION_NAME.
        /// The returned string uses the logical names of the module and of the function if they are defined,
        /// otherwise the serial number of the module and the hardware identifier of the function
        /// (for example: MyCustomName.relay1)
        /// </summary>
        /// <returns> a string that uniquely identifies the function using logical names
        ///         (ex: MyCustomName.relay1)
        /// </returns>
        /// <exception cref="YAPI_Exception"> on error </exception>
        public virtual async Task<string> get_friendlyName()
        {
            YPEntry yp = _yapi._yHash.imm_resolveFunction(_className, _func);
            return yp.getFriendlyName(_yapi);
        }

#pragma warning restore 1998

        public override string ToString()
        {
            return describe();
        }

        internal virtual async void _parse(YJSONObject json, ulong msValidity)
        {
            _cacheExpiration = YAPI.GetTickCount() + msValidity;
            try {
                imm_parseAttr(json);
            } catch (Exception e) {
                _throw(YAPI.IO_ERROR, e.Message);
            }
            await _parserHelper();
        }

        protected string imm_escapeAttr(string changeval)
        {
            return YAPIContext.imm_escapeAttr(changeval);
        }

        // Change the value of an attribute on a device, and update cache on the fly
        // Note: the function cache is a typed (parsed) cache, contrarily to the agnostic device cache
        protected internal virtual async Task<int> _setAttr(string attr, string newval)
        {
            if (newval == null) {
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Undefined value to set for attribute " + attr);
            }
            string attrname = YAPIContext.imm_escapeAttr(attr);
            string extra = "/" + attrname + "?" + attrname + "=" + YAPIContext.imm_escapeAttr(newval) + "&.";
            await _devRequest(extra);
            if (_cacheExpiration != 0) {
                _cacheExpiration = YAPI.GetTickCount();
            }
            return YAPI.SUCCESS;
        }

        private async Task<byte[]> _request(string req_first_line, byte[] req_head_and_body)
        {
            YDevice dev = await getYDevice();
            return await dev.requestHTTPSync(req_first_line, req_head_and_body);
        }

        protected internal virtual async Task<int> _upload(string path, byte[] content)
        {
            YDevice dev = await getYDevice();
            await dev.requestHTTPUpload(path, content);
            return YAPI.SUCCESS;
        }

        protected internal virtual async Task<byte[]> _uploadEx(string path, byte[] content)
        {
            YDevice dev = await getYDevice();
            return await dev.requestHTTPUpload(path, content);
        }

        protected internal virtual async Task<int> _upload(string pathname, string content)
        {
            return await this._upload(pathname, YAPI.DefaultEncoding.GetBytes(content));
        }

        protected internal virtual async Task<byte[]> _download(string url)
        {
            string request = "GET /" + url + " HTTP/1.1\r\n\r\n";
            return await _request(request, null);
        }


        protected internal virtual string imm_json_get_key(byte[] json, string key)
        {
            YJSONObject obj = new YJSONObject(YAPI.DefaultEncoding.GetString(json));
            obj.parse();
            if (obj.has(key)) {
                string val = obj.getString(key);
                if (val == null) {
                    val = obj.ToString();
                }
                return val;
            }
            throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "No key " + key + "in JSON struct");
        }

        protected internal virtual string imm_json_get_string(byte[] json)
        {
            string s = YAPI.DefaultEncoding.GetString(json);
            try {
                YJSONString jstring = new YJSONString(s, 0, s.Length);
                jstring.parse();
                return jstring.getString();
            } catch (Exception e) {
                _throw(YAPI.IO_ERROR, e.Message);
                return "";
            }
        }

        protected internal virtual List<string> imm_json_get_array(byte[] json)
        {
            YJSONArray array = new YJSONArray(YAPI.DefaultEncoding.GetString(json));
            try {
                array.parse();
            } catch (Exception e) {
                _throw(YAPI.IO_ERROR, e.Message);
            }
            List<string> list = new List<string>();
            int len = array.Length;
            for (int i = 0; i < len; i++) {
                try {
                    YJSONContent o = array.get(i);
                    list.Add(o.toJSON());
                } catch (Exception e) {
                    _throw(YAPI.IO_ERROR, e.Message);
                }
            }
            return list;
        }


        internal virtual string imm_get_json_path_struct(YJSONObject jsonObject, string[] paths, int ofs)
        {
            string key = paths[ofs];
            if (!jsonObject.has(key)) {
                return "";
            }

            YJSONContent obj = jsonObject.get(key);
            if (obj != null) {
                if (paths.Length == ofs + 1) {
                    return obj.toJSON();
                }

                if (obj is YJSONArray) {
                    return imm_get_json_path_array(jsonObject.getYJSONArray(key), paths, ofs + 1);
                } else if (obj is YJSONObject) {
                    return imm_get_json_path_struct(jsonObject.getYJSONObject(key), paths, ofs + 1);
                }
            }
            return "";
        }

        private string imm_get_json_path_array(YJSONArray jsonArray, string[] paths, int ofs)
        {
            int key = Convert.ToInt32(paths[ofs]);
            if (jsonArray.Length <= key) {
                return "";
            }

            YJSONContent obj = jsonArray.get(key);
            if (obj != null) {
                if (paths.Length == ofs + 1) {
                    return obj.ToString();
                }

                if (obj is YJSONArray) {
                    return imm_get_json_path_array(jsonArray.getYJSONArray(key), paths, ofs + 1);
                } else if (obj is YJSONObject) {
                    return imm_get_json_path_struct(jsonArray.getYJSONObject(key), paths, ofs + 1);
                }
            }
            return "";
        }


        protected internal virtual string imm_get_json_path(string json, string path)
        {
            YJSONObject jsonObject = null;
            try {
                jsonObject = new YJSONObject(json);
                jsonObject.parse();
            } catch (Exception) {
                return "";
            }
            string[] split = path.Split(new char[] { '\\', '|' });
            return imm_get_json_path_struct(jsonObject, split, 0);
        }

        internal virtual string imm_decode_json_string(string json)
        {
            if (json.Length == 0) {
                return "";
            }
            try {
                YJSONString ystr = new YJSONString(json, 0, json.Length);
                ystr.parse();
                return ystr.getString();
            } catch (Exception) {
                return "";
            }
        }

        // Load and parse the REST API for a function given by class name and
        // identifier, possibly applying changes
        // Device cache will be preloaded when loading function "module" and
        // leveraged for other modules
        internal virtual async Task<YJSONObject> _devRequest(string extra)
        {
            YDevice dev = await getYDevice();
            _hwId = _yapi._yHash.imm_resolveHwID(_className, _func);
            string[] split = _hwId.Split(new char[] { '\\', '.' });
            _funId = split[1];
            _serial = split[0];
            YJSONObject loadval = null;
            if (extra.Equals("")) {
                // use a cached API string, without reloading unless module is
                // requested
                YJSONObject jsonval = await dev.requestAPI();
                try {
                    loadval = jsonval.getYJSONObject(_funId);
                } catch (Exception) {
                    throw new YAPI_Exception(YAPI.IO_ERROR,
                        "Request failed, could not parse API result for " + dev);
                }
            } else {
                dev.imm_clearCache();
            }
            if (loadval == null) {
                // request specified function only to minimize traffic
                if (extra.Equals("")) {
                    string httpreq = "GET /api/" + _funId + ".json";
                    string yreq = await dev.requestHTTPSyncAsString(httpreq, null);
                    try {
                        loadval = new YJSONObject(yreq);
                        loadval.parse();
                    } catch (Exception) {
                        throw new YAPI_Exception(YAPI.IO_ERROR, "Request failed, could not parse API value for "
                                                                + httpreq);
                    }
                } else {
                    string httpreq = "GET /api/" + _funId + extra;
                    await dev.requestHTTPAsync(httpreq, null, null, null);
                    return null;
                }
            }
            return loadval;
        }

        internal virtual async Task<YDevice> getYDevice()
        {
            return await _yapi.funcGetDevice(_className, _func);
        }

        // Method used to cache DataStream objects (new DataLogger)
        internal virtual YDataStream imm_findDataStream(YDataSet dataset, string def)
        {
            string key = dataset.get_functionId() + ":" + def;
            if (_dataStreams.ContainsKey(key)) {
                return _dataStreams[key];
            }

            List<int> words = YAPIContext.imm_decodeWords(def);
            if (words.Count < 14) {
                _throw(YAPI.VERSION_MISMATCH, "device firmware is too old");
                return null;
            }

            YDataStream newDataStream = new YDataStream(this, dataset, words);
            _dataStreams[key] = newDataStream;
            return newDataStream;
        }

        // Method used to clear cache of DataStream object (undocumented)
        public virtual void imm_clearDataStreamCache()
        {
            _dataStreams.Clear();
        }

        /// <summary>
        /// Checks if the function is currently reachable, without raising any error.
        /// If there is a cached value for the function in cache, that has not yet
        /// expired, the device is considered reachable.
        /// No exception is raised if there is an error while trying to contact the
        /// device hosting the function.
        /// </summary>
        /// <returns> true if the function can be reached, and false otherwise </returns>
        public virtual async Task<bool> isOnline()
        {
            // A valid value in cache means that the device is online
            if (_cacheExpiration > YAPI.GetTickCount()) {
                return true;
            }
            try {
                // Check that the function is available without throwing exceptions
                await load(await _yapi.GetCacheValidity());
            } catch (YAPI_Exception) {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Returns the numerical error code of the latest error with the function.
        /// This method is mostly useful when using the Yoctopuce library with
        /// exceptions disabled.
        /// </summary>
        /// <returns> a number corresponding to the code of the latest error that occurred while
        ///         using the function object </returns>
        public virtual Task<int> get_errorType()
        {
            return Task.FromResult(_lastErrorType);
        }

        public virtual int imm_get_errorType()
        {
            return _lastErrorType;
        }


        public virtual int ErrorType
        {
            get { return _lastErrorType; }
        }

        public virtual int errorType()
        {
            return _lastErrorType;
        }

        public virtual int errType()
        {
            return _lastErrorType;
        }

        /// <summary>
        /// Returns the error message of the latest error with the function.
        /// This method is mostly useful when using the Yoctopuce library with
        /// exceptions disabled.
        /// </summary>
        /// <returns> a string corresponding to the latest error message that occured while
        ///         using the function object </returns>
        public virtual string get_errorMessage()
        {
            return _lastErrorMsg;
        }

        public virtual string ErrorMessage
        {
            get { return _lastErrorMsg; }
        }

        public virtual string errorMessage()
        {
            return _lastErrorMsg;
        }

        public virtual string errMessage()
        {
            return _lastErrorMsg;
        }


        /// <summary>
        /// Invalidates the cache. Invalidates the cache of the function attributes. Forces the
        /// next call to get_xxx() or loadxxx() to use values that come from the device.
        /// 
        /// 
        /// </summary>
        public virtual async Task clearCache()
        {
            try {
                YDevice dev = await getYDevice();
                dev.imm_clearCache();
            } catch (YAPI_Exception) {
            }
            if (_cacheExpiration != 0) {
                _cacheExpiration = YAPI.GetTickCount();
            }
        }


        /// <summary>
        /// Preloads the function cache with a specified validity duration.
        /// By default, whenever accessing a device, all function attributes
        /// are kept in cache for the standard duration (5 ms). This method can be
        /// used to temporarily mark the cache as valid for a longer period, in order
        /// to reduce network traffic for instance.
        /// </summary>
        /// <param name="msValidity"> : an integer corresponding to the validity attributed to the
        ///         loaded function parameters, in milliseconds
        /// </param>
        /// <returns> YAPI.SUCCESS when the call succeeds.
        /// </returns>
        /// <exception cref="YAPI_Exception"> on error </exception>
        public virtual async Task<int> load(ulong msValidity)
        {
            YJSONObject json_obj = await _devRequest("");
            _parse(json_obj, msValidity);
            return YAPI.SUCCESS;
        }

        /// <summary>
        /// Gets the YModule object for the device on which the function is located.
        /// If the function cannot be located on any module, the returned instance of
        /// YModule is not shown as on-line.
        /// </summary>
        /// <returns> an instance of YModule </returns>
        public virtual async Task<YModule> get_module()
        {
            // try to resolve the function name to a device id without query
            if (_serial != null && !_serial.Equals("")) {
                return YModule.FindModuleInContext(_yapi, _serial + ".module");
            }

            int ofs = _func.IndexOf('.');
            if (ofs == -1) {
                try {
                    string serial = _yapi._yHash.imm_resolveSerial(_className, _func);
                    return YModule.FindModuleInContext(_yapi, serial + ".module");
                } catch (YAPI_Exception) {
                }
            }

            if (ofs >= 0) {
                try {
                    string serial = _func.Substring(0, ofs);
                    return YModule.FindModuleInContext(_yapi, serial + ".module");
                } catch (YAPI_Exception) {
                }
            }

            try {
                // device not resolved for now, force a communication for a last chance resolution
                if (await load(await _yapi.GetCacheValidity()) == YAPI.SUCCESS) {
                    string serial = _yapi._yHash.imm_resolveSerial(_className, _func);
                    return YModule.FindModuleInContext(_yapi, serial + ".module");
                }
            } catch (YAPI_Exception) {
            }
            // return a true yFindModule object even if it is not a module valid for communicating
            return YModule.FindModuleInContext(_yapi, "module_of_" + _className + "_" + _func);
        }


        public virtual async Task<YModule> module()
        {
            return await get_module();
        }

#pragma warning disable 1998

        /// <summary>
        /// Returns a unique identifier of type YFUN_DESCR corresponding to the function.
        /// This identifier can be used to test if two instances of YFunction reference the same
        /// physical function on the same physical device.
        /// </summary>
        /// <returns> an identifier of type YFUN_DESCR.
        /// 
        /// If the function has never been contacted, the returned value is YFunction.FUNCTIONDESCRIPTOR_INVALID. </returns>
        public virtual async Task<string> get_functionDescriptor()
        {
            // try to resolve the function name to a device id without query
            try {
                return _yapi._yHash.imm_resolveHwID(_className, _func);
            } catch (YAPI_Exception) {
                return FUNCTIONDESCRIPTOR_INVALID;
            }
        }


        /// <summary>
        /// Returns the value of the userData attribute, as previously stored using method
        /// set_userData.
        /// This attribute is never touched directly by the API, and is at disposal of the caller to
        /// store a context.
        /// </summary>
        /// <returns> the object stored previously by the caller. </returns>
        public virtual async Task<object> get_userData()
        {
            return _userData;
        }


        /// <summary>
        /// Stores a user context provided as argument in the userData attribute of the function.
        /// This attribute is never touched by the API, and is at disposal of the caller to store a context.
        /// </summary>
        /// <param name="data"> : any kind of object to be stored
        ///  </param>
        public virtual async Task set_userData(object data)
        {
            _userData = data;
        }
#pragma warning restore 1998

        public async Task<string> get_debugMsg()
        {
            YDevice device = await getYDevice();
            return device.get_debugMsg();
        }
    }
}