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


//严格模式
/*-------------------------------------------------------------------------------------------*/	
'use strict';
/*-------------------------------------------------------------------------------------------*/	
    

//查询构造 初始化
/*-------------------------------------------------------------------------------------------*/	    
    

class orm
{
    constructor(main='main')
    {
        this.dbbase=main
        this._eng=$DbMysql.$info.able?'`':''
        this._id=''
        this._descript=''
        this._sql=''
        this._field=''
        this._table=''
        this._where=''
        this._left=''
        this._right=''
        this._key=''
        this._value=''
        this._limit=''
        this._group=''
        this._tableDelete=''
        this._order=''
        this._having=''
        this._cache=false
        this._multi=false
        this._cache_time=3600
    }
    
    db(main='main')
    {
        this.dbbase=main
        return this
    }

    eng(eng='`')
    {
        if(eng=='postgresql') this._eng=''
        else if(eng=='mysql') this._eng='`' 
        else this._eng=eng
        if(this._eng=='mongo')
        {
            this.mongo=$DbMongo.$con[this.dbbase]
        }
        return this
    }

    multi()
    {
        this._multi=true
    }

    id(i='id')
    {
        this._id='returning '+i
        return this
    }

    idList()
    {
        this._idlist=true
        return this
    }

    com(that,obj,tag=',')
    {
        if(!obj)return ''
        if(obj instanceof Object)
        {
            let make=(obj,iterator,tagBool,tagStr)=>
            {
                if(obj[iterator] && obj[iterator]['handle'] && obj[iterator]['handle'].indexOf('@')===0)
                {
                    if(tagBool)this._value+=','+obj[iterator]['value'];
                    that+=tag+tagStr+iterator+tagStr+' '+obj[iterator]['handle'].slice(1)+obj[iterator]['value'];
                }
                else
                {
                    if(tagBool)this._value+=',\''+obj[iterator]+'\'';
                    that+=tag+tagStr+iterator+tagStr+'=\''+obj[iterator]+'\'';
                }
            }

            let back=(iterator,obj)=>
            {
                if(iterator.indexOf('.')==-1)
                {
                    if(tag==',')
                    {
                        this._key+=','+this._eng+iterator+this._eng;
                        make(obj,iterator,true,this._eng)                       
                    }
                    else
                    {
                        make(obj,iterator,false,this._eng)
                    }
                }
                else
                {
                    if(tag==',')
                    {
                        this._key+=','+iterator;
                        make(obj,iterator,true,'')
                    }
                    else
                    {
                        make(obj,iterator,false,'')
                    }
                }
            }

            for (const iterator in obj) 
            {
                if(Array.isArray(obj[iterator]))
                {
                    for (let index = 0; index < obj[iterator].length; index++) 
                    {
                        back(iterator,{[iterator]:obj[iterator][index]})
                    }
                }
                else
                {
                    back(iterator,obj)
                }
            }
        }
        else
        {
            that+=tag+obj;
        }
        return that
    }

    comKey(that,arr)
    {
        if(!arr)return ''
        if(Array.isArray(arr))
        {
            for (const iterator of arr) 
            {
                if(iterator.indexOf('.')==-1)
                {
                    that+=','+this._eng+iterator+this._eng;
                }
                else
                {
                    that+=','+iterator;
                }   
            }
        }
        else
        {
            that+=','+arr;
        }
        return that
    }

    async comQer(sql,con)
    {
        let result

        let make=async ()=>
        {
            if(this._eng==='mongo')return sql
            if(con)
            {
                result=await con.querys(sql,undefined,this._descript)
            }
            else
            {
                if(this._eng==='')
                { 
                    result=await $Sys.model('/db/postgresql','query',[sql,this.dbbase,undefined,this._descript])
                }
                else
                {
                    result=await $Sys.model('/db/mysql','query',[sql,this.dbbase,undefined,this._descript])
                } 	
            }
            
            return result;
        }

        if(this._cache)
        {
            return $Sys.model
            (
                '/db/cache','get',
                [{key:$Sys.model('/fun/com','sha1',[sql]),timeout:this._cache_time,awaitSet:true,fun:make}]
            )
        }
         
        return make();
    } 
    
    session(fun)
    {
        let tag='mysql'
        if(this._eng=='mongo')tag=this._eng
        else if(this._eng!='`')tag='postgresql'
        if(!fun)$Sys.err({message:'未传入事物函数'})
        return $Sys.model
        (
            '/db/'+tag,'transaction',
            [
                async function(con)
                {
                    return await fun(con)
                },this.dbbase
            ]
        )
    }

    cache(timeout=3600)
    {
        if(!timeout)return this
        this._cache=true
        this._cache_time=timeout
        return this;
    }
    
    descript(descri='')
    {
        this._descript=descri;
        return this;
    }

    field(obj)
    {
        if(!obj)return this
        if(this._eng==='mongo')
        {
            this._field={...this._field,...obj}
            return this
        }
        let str=this.com('',obj)
        this._field+=str
        return this;
    }

    key(arr)
    {
        if(!arr)return this
        if(this._eng==='mongo')
        {
            if(!this._key)this._key={projection:{}}
            for (let index = 0; index < arr.length; index++) 
            {
                this._key.projection[arr[index]]=false
            }
            return this
        }
        let str=this.comKey('',arr);
        this._key+=str;
        return this;
    }

    tableDelete(arr)
    {
        if(!arr)return this
        let str=this.comKey('',arr);
        this._tableDelete+=str;
        return this;
    }

    value(arr)
    {
        if(!arr)return
        if(Array.isArray(arr))
        {
            for (const iterator of arr) 
            {
                if(isNaN(iterator))
                {
                    this._value+=','+iterator;
                }
                else
                {
                    this._value+=',\''+iterator+'\'';
                }   
            }
        }
        else
        {
            this._value+=','+arr;
        }
        return this;
    }

    where(obj,type='and') 
    {
        if(!obj)return this
        if(this._eng==='mongo')
        {
            this._where={...this._where,...obj}
            return this
        }
        let str=this.com('',obj,` and `);
        this._where+=' '+type+' ('+str.slice(4)+' )'
        return this;
    }

    having(obj)
    {
        if(!obj)return this
        let str=this.com('',obj,' and ');
        this._having+=str
        return this;
    }

    table(arr)
    {
        if(!arr)return this
        if(this._eng==='mongo')
        {
            if(!this._table)
            {
                this.mongo=this.mongo.collection(arr)
                this._table=arr
            }
            return this
        }
        let str=this.comKey('',arr);
        this._table+=str;
        return this;
    }

    left(table,obj)
    {
        if(!table)return this
        let str=this.com('',obj)
        str=str.slice(1)
        this._left+=' left join '+table+ (str?(' on '+str):'');
        return this;
    }

    right(table,obj)
    {
        if(!table)return this
        let str=this.com('',obj)
        str=str.trim(str)
        this._left+=' right join '+table+ (str?(' on '+str):'');
        return this;
    }

    limit(a,b)
    {
        if(a===undefined)return this
        if(this._eng==='mongo')
        {
            this._limit=[a,b]
            return this
        }
        this._limit=` limit ${a},${b} `
        return this;
    }

    page(a,b)
    {
        if(!a)return this
        if(this._eng==='mongo')
        {
            this._limit=[a*b,b]
            return this
        }
        if(b===undefined)return $Sys.err({code:1,message:'请输入页码'})
        this._limit=` limit ${a*b},${b} `
        return this;
    }

    order(field,or='asc')
    {
        if(!field)return this
        if(this._eng==='mongo')
        {
            this._order=[field,or]
            return this
        }
        this._order=` order by ${field} ${or}`
        return this;
    }

    group(field)
    {
        if(!field)return this
        if(this._eng==='mongo')
        {
            this._group=field
            return this
        }
        this._group=` group by ${field}`
        return this;
    }

    selectLock(con)
    {
        return this.select(con,' for update ')
    }    

    async select(con,tag='')
    {
        if(this._eng==='mongo')
        {
            this.mongo=this.mongo.find(this._where?this._where:{},this._key,con)
            if(this._limit)
            {
                this.mongo=this.mongo.skip(this._limit[0]).limit(this._limit[1])
            }
            if(this._group)
            {
                this.mongo=this.mongo.group(this._group)
            }
            if(this._order)
            {
                let or=this._order
                if(this._order=='asc')or=1
                else or=-1
                
                this.mongo=this.mongo.sort(this._order[0],or)
            }
            let data=await this.mongo.toArray()
            return this.comQer(data);
        }
        let _key=this._key.slice(1)
        let _table=this._table.slice(1)
        let _where=this._where.slice(4)
        let _left=this._left.slice(1)
        let _right=this._right.slice(1)
        let _having=this._having.slice(4)

        let sql=
        `
            select ${_key} from ${_table} ${_left} ${_right}  
            ${_where?('where '+_where):''} ${this._group}  ${_having?('having '+_having):''} ${this._order} ${this._limit} ${tag}
        `
        let data= await this.comQer(sql,con);
        if(data.rows)return data.rows
        return data
    }

    async insert(con)
    {
        if(this._eng==='mongo')
        {
            this.mongo=await this.mongo.insert(this._field?this._field:{},con)
            if(this._idlist)this.mongo=this.mongo.insertedIds
            else this.mongo=this.mongo.insertedIds[0]
            let data= this.mongo
            return this.comQer(data);
        }

        let _table=this._table.slice(1)
        let _key=this._key.slice(1)
        let _value=this._value.slice(1)
        
        let sql=
        `
            insert into ${_table} (${_key}) VALUES (${_value})  ${this._id}
        `
        let result= await this.comQer(sql,con);
        if(this._eng==='')
        {
            if(this._idlist)
            {
                result=result.rows;
            }
            else
            {
                result=(this._id && result.rowCount)?result.rows[0].id:result.rowCount;
            }
        } 
        else
        {
            result=result.insertId
        }
        
        return result
    }

    async delete(con)
    {
        if(this._eng==='mongo')
        {
            this.mongo=await this.mongo.remove(this._where?this._where:{},con)
            
            let data= this.mongo.result?this.mongo.result.n:0
            return this.comQer(data);
        }
        let _table=this._table.slice(1)
        let _where=this._where.slice(4)
        let _left=this._left.slice(1)
        let _right=this._right.slice(1)
        let _having=this._having.slice(4)
        let _tableDelete=this._tableDelete.slice(1)
        
        let sql=
        `
            delete ${_tableDelete} from ${_table} ${_left} ${_right}  
            ${_where?('where '+_where):''} ${this._group} ${_having?('having '+_having):''}  ${this._order}   ${this._limit}
        `
        let result= await this.comQer(sql,con);

        if(this._eng==='')
        {
            if(this._idlist)
            {
                result=result.rows;
            }
            else
            {
                result=result.rowCount;
            }
        } 
        else
        {
            result=result.affectedRows
        }
        
        return result
    }

    async update(con)
    {
        if(this._eng==='mongo')
        {
            this.mongo=await this.mongo.update(this._where?this._where:{},this._field?this._field:{},{upsert:false,multi:this._multi},con)
            let data= this.mongo.result?this.mongo.result.nModified:this.mongo.result
            return this.comQer(data);
        }
        let _table=this._table.slice(1)
        let _where=this._where.slice(4)
        let _field=this._field.slice(1)
        let _having=this._having.slice(4)
        
        let sql=
        `
            update ${_table} set ${_field}  
            ${_where?('where '+_where):''} ${this._group} ${_having?('having '+_having):''}  ${this._order}   ${this._limit}
        `
        
        let result= await this.comQer(sql,con);
        if(this._eng==='')
        {
            if(this._idlist)
            {
                result=result.rows;
            }
            else
            {
                result=result.rowCount;
            }
        } 
        else
        {
            result=result.affectedRows
        }
        return result
    }

    async insertUpdate(con)
    {
        if(this._eng==='mongo')
        {
            let data
            this.mongo=await this.mongo.update(this._where?this._where:{},this._field?this._field:{},{upsert:true},con)
            this.mongo=this.mongo.result
            if(this.mongo && this.mongo.nModified)
            {
                if(this.mongo.upserted)
                {
                    if(this._idlist) data= [this.mongo.upserted,0]
                    else data= [this.mongo.upserted[0],0]
                }
                else
                {
                    data= [0,1]
                }
            }
            else
            {
                data= [0,0]
            }
            return this.comQer(data);
        }
        let _table=this._table.slice(1)
        let _key=this._key.slice(1)
        let _value=this._value.slice(1)
        let _field=this._field.slice(1)
        
        let sql=
        `
            insert ${_table} (${_key}) VALUES (${_value})
            ON DUPLICATE KEY UPDATE 
            ${_field} ${this._id}
        `
        let result= await this.comQer(sql,con);

        if(this._eng==='')
        {
            if(this._idlist)
            {
                result=result.rows;
            }
            else
            {
                result=[(this._id && result.rowCount)?result.rows[0].id:result.rowCount,result.rowCount]
            }
        } 
        else
        {
            result=[result.insertId,result.affectedRows]
        }
        return result
    }
}

module.exports=orm;