
/*=============================================================================================
	author:ZhaoJun
	version:1.2
	email:2732810281@qq.com
=============================================================================================*/


//严格模式
/*-------------------------------------------------------------------------------------------*/	
'use strict';
/*-------------------------------------------------------------------------------------------*/	
const redis=require("ioredis");

//redis 初始化
/*-------------------------------------------------------------------------------------------*/	    
    
class db_redis
{
    static com(data,message,tag)
    {
        if(data[0][1])
        {
            return data[0][1]
        }
        else
        {
            let data1=''
            if(data[0][0])data1=data[0][0].toString()
            console.warn(`[redis]${message}\r\n${data1}\r\n`,data)
            $Sys.err({message:message});
        }
    }

    static param(data,tag)
    {
        if(data && Array.isArray(data))
        {
            let obj
            if(data.length%2!=0 || tag)
            {
                obj=[]
                for (let index = 0; index < data.length; index++) 
                {
                    let cur=data[index]
                    obj.push(db_redis.param(cur))
                }
            }
            else
            {
                obj={}
                for (let index = 0; index < data.length; index=index+2) 
                {
                    let cur=data[index]
                    let curNext=data[index+1]
                    if(Array.isArray(cur))
                    {
                        return db_redis.param(data,true)
                    }
                    if(Array.isArray(curNext))
                    {
                        obj[cur]=db_redis.param(curNext)
                    }
                    else
                    {
                        obj[cur]=curNext
                    }
                }
            }
            return obj
        }
        return data
    }

    static async con() 
    {	 
        const cdb=$DbRedis.$opt
        for (const key in cdb) 
        {
            let cur=cdb[cdb]
            $DbRedis.$con[key] = new redis(cur);
            if(!$DbRedis.$con[key])
            {
                console.error(`REDIS进程：`+process.pid+`------链接错误[${key}]`);
            }
            else
            {
                db_redis.init($DbRedis.$con[key])
                console.info(`REDIS进程：`+process.pid+`------链接成功[${key}]`);
            }
        }
    }

    static async pipe(list,db='main')
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ...list
        ]).exec();
        return db_redis.com(data,'执行自定义命令错误');
    }

    static async multi(list,db='main')
    {
        let data=await $DbRedis.$con[db].multi
        ([
            ...list
        ]).exec();
        return db_redis.com(data,'执行事物命令错误');
    }

    static async listener()
    {
        const cdb=$DbRedis.$opts
        var redis=require("ioredis");
        for (const key in cdb) 
        {
            let cur=cdb[key]
            let redisS = new redis(cur);
            if(!redisS)
            {
                console.error(`REDIS监听进程：`+process.pid+`------链接错误[${key}]`);
            }
            else
            {
                
                let fun=cdb.fun
                await redisS.subscribe(...cdb[key].listener)
                redisS.on
                (
                    'message', function (channel, message) 
                    {
                        fun&&fun(channel, message)
                    }
                )
                console.info(`REDIS监听进程：`+process.pid+`------链接成功[${key}]`);
            }
        }
    }

    static async config(param=['notify-keyspace-events','Ex'],tag=true,db='main')
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['config','set',...param]
        ]).exec();
        if(tag)
        {
            data=await $DbRedis.$con[db].pipeline
            ([
                ['config','rewrite']
            ]).exec();
            return db_redis.com(data,'配置保存失败');
        }
        else
        {
            return db_redis.com(data,'配置失败');
        }
    }

    static async init(redis)
    {
        return await redis.defineCommand
        (
            'unLock', 
            {
                numberOfKeys: 1,
                lua: `if redis.call("get",KEYS[1]) == ARGV[1] then
                        return redis.call("del",KEYS[1])
                    else
                        return 0
                    end`
            }
        );
    }

    static async lock(key,val,time=30000,db='main')
    {
        return await $DbRedis.$con[db].pipeline
        ([
            ['set', '$@'+key, val,'NX','PX',time]
        ]).exec();
    }

    static async unLock(key,val,db='main')
    {
        return await $DbRedis.$con[db].unLock('$@'+key,val);
    }

    static async lockPipe(key,val,fun,time=30000,db='main')
    {
        let resu=await db_redis.lock(key,val,time,db);
        if(!resu[0] || resu[0][0])return false;
        resu=await fun();
        await db_redis.unLock('$@'+key,val,db);
        return resu;
    }

    //只有在消费组才有ack，其他的读取操作都是无状态的

    //特殊id,
    //(-，+)[xrange，xpending]
    //($)[xgroup create,xgroup setid]
    //(>)[xreadgroup]
    //这些只有在特定语法可以

    //不完全ID流的ID由两部分组成，一个Unix毫秒时间戳和一个为同一毫秒插入的序列号
    //XRANGE somestream 1526985054069 1526985055069
    //在这种情况中，将会使用-0自动补全ID
    //0表示流中第一项的ID


    //添加任务到队列，id为*时自动生成id
    static async queueXadd({key='queue',count,id="*",max=0,list=[],db='main'})
    {
        let arr=[];
        if(max)
        {
            arr=['maxlen','~',count]
        }
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xadd', '$@#'+key,...arr, id,...list]
        ]).exec();
        return db_redis.com(data,'队列添加失败');
    }

    //读取，timeout超时秒,0代表一直等待
    static async queueXRead({key='queue',begin='0',count=1,timeout='10',db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xread', 'block',timeout,'count',count,'streams','$@#'+key, begin]
        ]).exec();
        return db_redis.param(db_redis.com(data,'队列查询范围失败'));
    }

    //查询一个范围的队列
    //- 实际上与指定0-0完全一样，而+则相当于18446744073709551615-18446744073709551615
    static async queueXrange({key='queue',min="-",max='+',count=1,db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xrange', '$@#'+key, min,max,count]
        ]).exec();
        return db_redis.param(db_redis.com(data,'队列查询范围失败'));
    }


    //创建一个消费者并且消费，timeout超时秒,0代表一直等待
    //特殊ID>，意味着消费者希望只接收从未发送给任何其他消费者的消息。这意思是说，请给我新的消息。
    //任意其他的ID，即0或任意其他有效ID或不完整的ID（只有毫秒时间部分），将返回发送命令的消费者的待处理条目。
    //所以，基本上如果ID不是>，命令将让客户端访问它的待处理条目（已发送给它，但尚未确认的条目）。
    static async queueXreadgroup({key='queue',name='queue',user='queue',count=1,next='>',timeout='10',db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xreadgroup','block',timeout,'GROUP','$@#-'+name, '$@#--'+user,'count',count,'streams','$@#'+key,next]
        ]).exec();
        return db_redis.com(data,'队列创建消费者失败')[0][1];
    }


    //查询消费情况
    //type 取值，groups ，consumers
    static async queueXinfo({key=['queue','queue'],type='stream',db='main'})
    {
        key[0]='$@#'+key[0]
        if(type=='consumers')
        {
            key[1]='$@#-'+key[1]
        }
        else
        {
            if(key[1])key.pop()
        }

        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xinfo', type, ...key]
        ]).exec();
        return db_redis.param(db_redis.com(data,'队列查询消费情况失败'));
    }


    //创建消费组
    // begin参数 ‘$’流中最后一项的ID,‘0’流中第一项的ID
    static async queueXgroup({key='queue',name='queue',begin='0',db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xgroup','create', '$@#'+key, '$@#-'+name, begin]
        ]).exec();
        return db_redis.com(data,'队列创建消费组失败');
    }

    //重新设置消费组，消费起始id
    static async queueXgroupSetid({key='queue',name='queue',begin="0",db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xgroup','setid', '$@#'+key, '$@#-'+name,'$@#--'+user, begin]
        ]).exec();
        return db_redis.com(data,'队列创建消费组失败');
    }
    

    //移除消费组里的消费者
    static async queueXgroupDelconsumer({key='queue',name='queue',user='queue',db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xgroup','delconsumer', '$@#'+key, '$@#-'+name,'$@#--'+user]
        ]).exec();
        return db_redis.com(data,'队列创建消费组失败');
    }

    //销毁消费组
    static async queueXgroupDestroy({key='queue',name='queue',db='main'})
    {
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xgroup','destroy', '$@#'+key, '$@#-'+name]
        ]).exec();
        return db_redis.com(data,'队列创建消费组失败');
    }

    //xack来确认消息id
    static async queueXack({key='queue',name='queue',id=0,db='main'})
    { 
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xack','$@#'+key, '$@#-'+name,id]
        ]).exec();
        return db_redis.com(data,'队列确认消息失败');
    }

    //返回未ack的消息
    static async queueXPending({key='queue',name='queue',user='queue',min='-',max='+',count='10',db='main'})
    {
        let list=['xpending','$@#'+key, '$@#-'+name,min,max,count,'$@#--'+user]
        if(user===false)list.pop()
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline([list]).exec();
        return db_redis.com(data,'队列返回未ack消息失败');
    }

    //获取未ack的权限
    static async queueXclaim({key='queue',name='queue',user='queue',timeout=360000,id=false,db='main'})
    {
        if(id===false)return false;
        //timeout超时秒,360秒（超过这个时间才能获取）
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xclaim','$@#'+key,'$@#-'+name, '$@#--'+user,timeout,id]
        ]).exec();
        return db_redis.com(data,'队列获取未ack的权限失败');
    }

    //队列长度
    static async queueXlen({key='queue',db='main'})
    {
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xlen','$@#'+key]
        ]).exec();
        return db_redis.com(data,'队列获取长度失败');
    }

    //删除消息id
    static async queueXdel({key='queue',id='0',db='main'})
    {
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xdel','$@#'+key,id]
        ]).exec();
        return db_redis.com(data,'队列删除消息失败');
    }

    //删除消息队列
    static async queueDel({key='queue',db='main'})
    {
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['del','$@#'+key]
        ]).exec();
        return db_redis.com(data,'队列删除所有消息失败');
    }


    //修剪队列,保留最新count数量
    //参数~的意思是，不是真的需要精确的count个项目。它可以多几十个条目，但决不能少于1000个
    static async queueTrim({key='queue',tag='~',count=10000,db='main'})
    {
        //timeout超时秒,0代表等待
        let data=await $DbRedis.$con[db].pipeline
        ([
            ['xtrim','$@#'+key,'maxlen',tag,count]
        ]).exec();
        return db_redis.com(data,'队列删除所有消息失败');
    }
}

module.exports=db_redis;