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


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

//创建表
/*-------------------------------------------------------------------------------------------*/	    
class build
{  
    static async isRegTable(type,objTag)
    {
        let result
        switch (type) 
        {
            case 'postgresql':
                result=
                `
                select relname as tablename,cast(obj_description(relfilenode,'pg_class') as varchar) as comment 
                from pg_class c 
                where  relkind = 'r' and relname not like 'pg_%' and relname not like 'sql_%'  
                and relchecks=0 order by relname 
                `
            break;
            case 'mysql':
                result=
                `
                select table_name tablename,table_comment comment
                from information_schema.tables 
                where table_schema = (select database())
                `
            break;
        }
        if(objTag)return result
        result =await build.query.comQer(result)
        if(result.rows)result=result.rows
        let arr=[]
        for (let index = 0; index < result.length; index++) 
        {
          arr.push(result[index].tablename)  
        }
        return arr
    }

    static async createTable(table,type)
    {   
        let sqlTable
        let get=(param,tag)=>param?((tag?tag:'')+param):''
        let gets=(param,tag)=>param?(param+(tag?('('+tag+')'):'')):''
        let str=''
        await build.createCom
        (
            table,(obj,key)=>
            {
                str+=','+key+' '+gets(obj.private.type,obj.private.length)+' '+get(((obj.private.default!=undefined)?("'"+obj.private.default+"'"):''),'default ')+' '+get(obj.private.meta)
            }
        )
        str=str.slice(1)
        sqlTable=`create table ${table.$name}(${str} )  `
        await build.query.comQer(sqlTable)
        await build.createField(table,table.$type)  
    } 

    static async isRegField(table,type)
    {
        let result
        switch (type) 
        {
            case 'postgresql':
                result=`select column_name as col from information_schema.columns where table_name = '${table}' `
            break;
            case 'mysql':
                result=`select COLUMN_NAME as col from INFORMATION_SCHEMA.COLUMNS where table_name = '${table}'`
            break;
        }
        result=await build.query.comQer(result)
        if(result.rows)result=result.rows
        let arr=[]
        for (let index = 0; index < result.length; index++) 
        {
            arr.push(result[index].col)  
        }
        return arr
    }

    static async createField(table,type)
    {
        let result=await build.isRegField(table.$name,table.$type)

        if((type=='mysql' && $DbMysql.$info.isDeleteField) ||  (type=='postgresql' && $DbPg.$info.isDeleteField))
        {
            let keys=Object.keys(table)
            for (let index = 0; index < result.length; index++) 
            {
                if(!keys.includes(result[index]))
                {
                    let tag
                    for (let ind = 0; ind < keys.length; ind++) 
                    {
                        let obj=table[keys[ind]]
                        if(obj && obj.private && obj.private.change==result[index])
                        {
                            tag=true
                            break
                        }
                    }
                    if(!tag)await build.query.comQer('alter table '+table.$name+' drop '+result[index])
                }
            }
        }

        await build.createCom
        (
            table,async (obj,key)=>
            {
                let get=(param,tag,sp)=>param?((tag?tag:'')+param):''
                let gets=(param,tag)=>param?(param+(tag?('('+tag+')'):'')):''
                let sql,sqlCom,sqlChg,sqlDef,sqlMeta,sqlType
                let chg=obj.private.change
                if(result.includes(key) || (chg && result.includes(chg)))
                {
                    if(result.includes(key) && (chg && result.includes(chg)))
                    {
                        console.warn('[表字段修改]冲突',key)
                        return
                    }
                    if(type=='mysql')
                    {
                        sql='alter table '+table.$name+' change '+(chg?chg:key)+' '+key+' '+gets(obj.private.type,obj.private.length)+' '+
                        get(((obj.private.default!=undefined)?("'"+obj.private.default+"'"):''),'default ')+' '+
                        get(obj.private.meta)+' '+get("'"+((obj.private.comment?obj.private.comment:obj.extends.descript)?(obj.private.comment?obj.private.comment:obj.extends.descript):'')+"'",'comment ',"'")
                    }
                    else if(type='postgresql')
                    {
                        if(chg)sqlChg=`alter table ${table.$name} rename ${table.$name}.${chg} to ${table.$name}.${key}`
                        
                        if(obj.private.type)sqlType=`ALTER TABLE ${table.$name} ALTER COLUMN ${key} TYPE ${gets(obj.private.type,obj.private.length)}`
                        if(obj.private.default!=undefined)sqlDef=`ALTER TABLE ${table.$name} ALTER COLUMN ${key} SET DEFAULT '${obj.private.default}'`
                        if(obj.private.meta)sqlMeta=`ALTER TABLE ${table.$name} ALTER COLUMN ${key} SET ${obj.private.meta}`
                        if((obj.private.comment?obj.private.comment:obj.extends.descript)) sqlCom=`COMMENT ON COLUMN ${table.$name}.${key} IS '${get((obj.private.comment?obj.private.comment:obj.extends.descript))}'`
                    }
                }
                else
                {
                    if(type=='mysql')
                    {
                        sql='alter table '+table.$name+' add '+key+' '+gets(obj.private.type,obj.private.length)+' '+
                        get(((obj.private.default!=undefined)?("'"+obj.private.default+"'"):''),'default ')+' '+
                        get(obj.private.meta)+' '+get("'"+((obj.private.comment?obj.private.comment:obj.extends.descript)?(obj.private.comment?obj.private.comment:obj.extends.descript):'')+"'",'comment ',"'")
                    }
                    else if(type='postgresql')
                    {
                        sql='alter table '+table.$name+' add '+key+' '+gets(obj.private.type,obj.private.length)+' '+
                        get(((obj.private.default!=undefined)?("'"+obj.private.default+"'"):''),'default ')+' '+get(obj.private.meta)
                        sqlCom=`COMMENT ON COLUMN ${table.$name}.${key} IS '${get((obj.private.comment?obj.private.comment:obj.extends.descript))}'`
                    }
                }
                if(sqlChg)await build.query.comQer(sqlChg)
                if(sql)await build.query.comQer(sql)
                if(sqlCom)await build.query.comQer(sqlCom)
                if(sqlType)await build.query.comQer(sqlType)
                if(sqlDef)await build.query.comQer(sqlDef)
                if(sqlMeta)await build.query.comQer(sqlMeta)
            }
        )
        await build.index(table)
        
    }

    static async index(table)
    {

        try 
        {
            let sql
            switch (table.$type) 
            {
                case 'mysql':
                    sql=`show index from ${table.$name}`
                break;
            
                case 'postgresql':
                    sql=
                    `
                        select 
                            indexrelname as Key_name
                        from 
                            pg_stat_user_indexes 
                        where
                            relname = '${table.$name}'
                    `
                break;
            }
            let result=await build.query.comQer(sql)
            if(result.rows)
            {
                let arr=[]
                for (let index = 0; index < result.rows.length; index++) 
                {
                   arr.push({Key_name:result.rows[index].key_name})
                }
                result=arr
            }
            
            for (let index = 0; index < result.length; index++) 
            {
                if(!result[index].Key_name)continue
                if(result[index].Key_name=='PRIMARY' || result[index].Key_name.indexOf('_pkey')!=-1)
                {
                    switch (table.$type) 
                    {
                        case 'mysql':
                            await build.query.comQer(`alter table ${table.$name} drop primary key`)
                        break;
                        case 'postgresql':
                            await build.query.comQer(`alter table ${table.$name} drop constraint ${result[index].Key_name}`)
                        break;
                    }
                }
                else
                {
                    switch (table.$type) 
                    {
                        case 'mysql':
                            await build.query.comQer(`alter table ${table.$name} drop index ${result[index].Key_name}`)
                        break;
                        case 'postgresql':
                            await build.query.comQer(`alter table ${table.$name} drop constraint ${result[index].Key_name}`).catch
                            (
                                async err=>
                                {
                                    await build.query.comQer(`DROP INDEX ${result[index].Key_name}`)
                                }
                            )
                        break;
                    }
                }
            }

            if(Array.isArray(table.$index))
            {
                for (let index = 0; index < table.$index.length; index++) 
                {
                    if(table.$type=='mysql')
                    {
                        if(table.$index[index].indexOf('CREATE ')===0)
                        {
                            await build.query.comQer(table.$index[index])
                            continue
                        }
                        await build.query.comQer(`alter table ${table.$name} add ${table.$index[index]}`)
                    }
                    else if(table.$type=='postgresql')
                    {
                        if(table.$index[index].indexOf('CREATE ')===0)
                        {
                            await build.query.comQer(table.$index[index])
                            continue
                        }
                        else if(table.$index[index].indexOf('(')==-1 && table.$index[index].indexOf(')')==-1)
                        {
                            await build.query.comQer(`CREATE INDEX ON ${table.$name} (${table.$index[index]})`)
                            continue
                        }
                        await build.query.comQer(`alter table ${table.$name} add ${table.$index[index]}`)
                    }
                    if(/PRIMARY /i.test()!=-1)
                    { 
                        let col=table.$index[index].replace(/.*?\((.*?)\)/,'$1')
                        if(col)
                        {
                            col=col.split(',')
                            let get=(param,tag)=>param?((tag?tag:'')+param):''
                            let gets=(param,tag)=>param?(param+(tag?('('+tag+')'):'')):''
                            for (let ind = 0; ind < col.length; ind++) 
                            {
                                let obj=table[col[ind].replace(/`/g,'')]
                                if(obj && obj.private && obj.private.auto)
                                {
                                    let sql,sqlMeta 
                                    if(table.$type=='mysql')
                                    {
                                        sql=sql='alter table '+table.$name+' change '+col[ind]+' '+col[ind]+' '+gets(obj.private.type,obj.private.length)+' auto_increment '+
                                        ' '+get(obj.private.meta)+' '+get("'"+((obj.private.comment?obj.private.comment:obj.extends.descript)?(obj.private.comment?obj.private.comment:obj.extends.descript):'')+"'",'comment ',"'")  
                                    }
                                    else if(table.$type=='postgresql')
                                    {
                                        await build.query.comQer('CREATE SEQUENCE vsmart_tablename_colname_seq').catch(err=>err)
                                        sql=`ALTER TABLE ${table.$name} ALTER COLUMN ${col[ind]} SET DEFAULT nextval('vsmart_tablename_colname_seq')`
                                        sqlMeta=`ALTER TABLE ${table.$name} ALTER COLUMN ${col[ind]} SET NOT NULL`
                                    }  
                                    if(sql)await build.query.comQer(sql) 
                                    if(sqlMeta)await build.query.comQer(sqlMeta)             
                                }
                            }
                        }
                    }
                }
            }
        } 
        catch (error) 
        { 
            throw Error(`[删除表索引]发生错误,结束构建:${error}`)
        }
    }

    static async createCom(table,fun)
    {
        for (const key in table) 
        {
            let obj=table[key]
            if(key.indexOf('$')===0)continue
            if(!obj || !obj.private)
            {
                console.error('[构建表]未找到属性',table+'-'+key)
                continue
            }
            await fun(obj,key)    
        }
    }

    static async makeTable (tag,table)
    {
        try
        {
            if(table.$change)
            {
                let sql=`alter table ${table.$change} rename to ${table.$name}` 
                await build.query.comQer(sql)
            }
            if(tag)
            {
                await build.createField(table,table.$type)       
            }
            else
            {
                await build.createTable(table,table.$type)
            }  

            if(table.$comment)
            {
                let sqlComment
                switch (table.$type) 
                { 
                    case 'postgresql':
                        sqlComment=`comment on table ${table.$name} is '${table.$comment}'`
                    break;
                    case 'mysql':
                        sqlComment=`alter table ${table.$name} comment '${table.$comment}'`
                    break;
                }
                if(sqlComment)await build.query.comQer(sqlComment)
            }

            if(table.$eng)
            {
                let sqlEng
                switch (table.$type) 
                { 
                    case 'postgresql':
                        //sqlEng=``
                    break;
                    case 'mysql':
                        sqlEng=`alter table ${table.$name} ENGINE '${table.$eng}'`
                    break;
                }
                if(sqlEng)await build.query.comQer(sqlEng)
            }

            if(table.$meta)
            {
                let sqlMeta
                switch (table.$type) 
                { 
                    case 'postgresql':
                        //sqlMeta=``
                    break;
                    case 'mysql':
                        sqlMeta=`alter table ${table.$name}  DEFAULT CHARACTER SET '${table.$meta}'`
                    break;
                }
                if(sqlMeta)await build.query.comQer(sqlMeta)
            }
        } 
        catch (error) 
        {
            throw Error(`[构建表${table.$name}]发生错误,结束构建：${error}`)
        }
    }

    static async exec(globalTag,arr)
    {
        function bool(key='')
        {
            if(arr)
            {
                if(arr.length===0)
                {
                    return false
                }
                else
                {
                    return arr.includes(key)
                } 
            }
            else
            {
                return true
            }
        }

        async function make(type) 
        {
            let dbTable
            let dbOpt
            
            if(type=='mysql')
            {
                dbTable=$DbMysql.$table
                dbOpt=$DbMysql.$opt
            }
            else
            {
                dbTable=$DbPg.$table
                dbOpt=$DbPg.$opt
            }

            for (const key in dbTable) 
            {
                if(dbOpt[key])
                {
                    build.query=$Sys.orm.db(key).eng(type)
                    let tables=await build.isRegTable(type)
                    
                    if((type=='mysql' && $DbMysql.$info.isDeleteTable) ||  (type=='postgresql' && $DbPg.$info.isDeleteTable))
                    {
                        if(!globalTag)await build.forFile()
                        setTimeout
                        (
                            async function()
                            {
                                let keys=Object.keys(dbTable[key])
                                for (let index = 0; index < tables.length; index++) 
                                {
                                    if(!keys.includes(tables[index]))
                                    {
                                        await build.query.comQer('drop table '+tables[index])
                                    }
                                }
                            }
                        )
                    }
                    
                    for (const keyField in dbTable[key]) 
                    {
                        if(bool(type+'@_'+key+'#_'+keyField)) await build.makeTable(tables.includes(keyField),dbTable[key][keyField])
                    } 
                }
            }
        }
        await make('mysql')
        await make('postgresql') 
         
        return true
    }

    static async forFile()
    {
        $DbMysql.$table={}
        $DbPg.$table={}
        $DbMysql.$rule={}
        $DbPg.$rule={}
        let arr=await $Load.forFile($Config.root+'/nodejs/mould')
        for (let index = 0; index < arr.length; index++) 
        {
            let df=arr[index]
            if(df.indexOf('_vsmartPrivate.js')!=-1)continue
            await build.requireCom(df)
        }
    }
    
    static async hotUpdate(df,tag,globalTag)
    { 
        if(!tag || df.indexOf('_vsmartPrivate.js')!=-1)return  
        let arr=[]
        let comTag=await build.requireCom(df,arr)
        try 
        {
            if($Config.tag.dbBuild)
            {
                console.warn('[构建]','正在进行,请等待...')
                return
            }
            $Config.tag.dbBuild=true
            if(!globalTag && ($DbMysql.$info.isAuto || $DbPg.$info.isAuto) && comTag)await build.exec(true,arr)   
        } 
        catch (error) 
        {
            console.error('[构建]发生错误',error)   
        }
        finally
        {
            $Config.tag.dbBuild=false
        }
    }

    static async hotUpdateInit(df,tag)
    {  
        await build.hotUpdate(df,tag,true)
    }

    static async requireCom(df,arrALl)
    {
        try 
        {
            let moud
            moud=require(df)
            if(!moud)return
            let object=Object.getOwnPropertyNames(moud)
            for (const val of object)  
            {    
                if(typeof moud[val]=='function')
                {
                    let obj=moud[val]()
                    if(!obj.$as)
                    {
                        console.warn('[生成表规则]未找到别名',df)
                        continue
                    }
                    obj.$name=val
                    let objcur
                    switch (obj.$type) 
                    {
                        case 'mysql':
                            $DbMysql.$table[obj.$as]=$DbMysql.$table[obj.$as]?$DbMysql.$table[obj.$as]:{}
                            $DbMysql.$table[obj.$as][val]=obj    
                            $DbMysql.$rule[obj.$as]=$DbMysql.$rule[obj.$as]?$DbMysql.$rule[obj.$as]:{} 
                            $DbMysql.$rule[obj.$as][val]=$DbMysql.$rule[obj.$as][val]?$DbMysql.$rule[obj.$as][val]:{}
                            objcur=$DbMysql.$rule[obj.$as][val]
                            $DbMysqlr[val]=objcur
                        break;
                        case 'postgresql':
                            $DbPg.$table[obj.$as]=$DbPg.$table[obj.$as]?$DbPg.$table[obj.$as]:{}
                            $DbPg.$table[obj.$as][val]=obj
                            $DbPg.$rule[obj.$as]=$DbPg.$rule[obj.$as]?$DbPg.$rule[obj.$as]:{}  
                            $DbPg.$rule[obj.$as][val]=$DbPg.$rule[obj.$as][val]?$DbPg.$rule[obj.$as][val]:{}
                            objcur=$DbPg.$rule[obj.$as][val] 
                            $DbPgr[val]=objcur
                        break;
                        case 'mongo':
                            $DbMongo.$rule[obj.$as]=$DbMongo.$rule[obj.$as]?$DbMongo.$rule[obj.$as]:{}  
                            $DbMongo.$rule[obj.$as][val]=$DbMongo.$rule[obj.$as][val]?$DbMongo.$rule[obj.$as][val]:{}
                            objcur=$DbMongo.$rule[obj.$as][val]
                            $DbMongor[val]=objcur
                        break;
                        default:
                            return
                    }

                    build.forParam(obj,objcur)
                    if(arrALl)arrALl.push(obj.$type+'@_'+obj.$as+'#_'+obj.$name)
                }

                try 
                {
                    if($ExtendPool.worker.able)
                    {
                        await $Sys.model('/fun/worker','share',['$DbMysqlr',$DbMysqlr])
                        await $Sys.model('/fun/worker','share',['$DbPgr',$DbPgr]) 
                        await $Sys.model('/fun/worker','share',['$DbMongor',$DbMongor]) 
                    }   
                }
                catch (error) 
                {
                    console.error('[生成文档]线程共享内存',error)  
                    return false  
                }
            }
        } 
        catch (error) 
        {
            console.error('[表规则生成]错误',error)
            return 
        }
        return true
    }

    static forParam(table,cur)
    {
        for (const key in table) 
        {
            let obj=table[key]
            if(key.indexOf('$')===0)continue
            if(!obj)obj={}
            if(!obj.extends)obj.extends={}
            cur[key]=obj.extends
        }
    }
    
}
/*-------------------------------------------------------------------------------------------*/	 
module.exports=build;