#include <vector>
#include <memory>
#include <iostream>

#include "authdb.h"
#include "index.h"

namespace authdb {
    AuthData::AuthData(const uuid::uuid &id){
         std::copy(&id.value[0],&id.value[16],&Data.ruid[0]);
         Data.start=0xFE;
         Data.storage=EmptyStorage;
         Data.type=DataType::EmptyData;
         memset(Data.fieldname,0,sizeof(Data.fieldname));
         Data.data=nullptr;
         Data.datasize=0;
        _next=nullptr;
    }

    AuthData::AuthData(const AuthData &src) : AuthData(src.Data.ruid){
        AuthData *dest=this;

        for(AuthData* cursrc=dest; cursrc!=nullptr; cursrc=cursrc->next()){
            memcpy(&dest->Data,&cursrc->Data,sizeof(AuthData::Record));
            dest->Data.datasize=cursrc->Data.datasize;
            dest->Data.data=new char[dest->Data.datasize];
            memcpy(dest->Data.data,cursrc->Data.data,cursrc->Data.datasize);
            if(cursrc->next()){
                dest->_next=new AuthData(cursrc->Data.ruid);
                dest=dest->next();
            }
        }
    }

    AuthData::~AuthData(){
    }

    AuthData *AuthData::next() const{
        return _next;
    };

    bool authdb::AuthData::empty(){
        if(!Data.data)
            return true;
        return false;
    };

    authdb::AuthData& AuthData::append(const AuthData &src){
        AuthData *curec=this,*next=this;
        if(!curec->_next && curec->Data.datasize==0){
            for(const AuthData *cursrc=&src; cursrc; cursrc=cursrc->next()){
                next=&next->append(cursrc->Data);
            }
            return *curec;
        }

        while(curec){
            if(!curec->_next){
                curec->_next=new AuthData(src);
                curec=curec->_next;
                break;
            }
            curec=curec->_next;
        };

        return *curec;

    }

    authdb::AuthData& AuthData::append(const AuthData::Record &src){
        AuthData *curec=this;

        if(!curec->_next && curec->Data.datasize==0){
            memcpy(&curec->Data,&src,sizeof(AuthData::Record));
            curec->Data.data=new char[curec->Data.datasize];
            memcpy(curec->Data.data,src.data,curec->Data.datasize);
            return *curec;
        }

        while(curec){
            if(!curec->_next){
                curec->_next=new AuthData(Data.ruid);
                curec=curec->_next;
                curec->append(src);
                break;
            }
            curec=curec->_next;
        };

        return *curec;
    }

    bool getRecord(authdb::AuthBackend &backend,AuthData &rec,int type){
        bool found=false;
        size_t rd=sizeof(authdb::AuthHeader),end=backend.end();

        while(rd+sizeof(AuthData::Record)<=end){
            std::shared_ptr<AuthData::Record> cur=std::make_shared<AuthData::Record>();
            backend.setPos(rd);
            backend.read(reinterpret_cast<unsigned char*>(cur.get()),sizeof(AuthData::Record));
            size_t next=backend.getPos()+cur->datasize;
            if(next<backend.getPos() || next>end) break;
            rd=next;
            if(uuid::uuid(cur->ruid)==rec.Data.ruid && cur->type == type){
                cur->data=new char[cur->datasize + 1];
                backend.read(reinterpret_cast<unsigned char*>(cur->data),cur->datasize);
                cur->data[cur->datasize] = '\0';
                rec.append(*cur);
                found=true;
                delete[] cur->data;
            }
        }

        return found;
    }

    void createRecord(authdb::AuthBackend &backend,AuthData &rec,int type){
        size_t wd=backend.end();

        for(AuthData* curec=&rec; curec; curec=curec->next()){
            curec->Data.type=type;
            backend.setPos(wd);
            backend.write(reinterpret_cast<unsigned char*>(curec),sizeof(struct AuthData::Record));
            backend.write(reinterpret_cast<unsigned char*>(curec->Data.data),curec->Data.datasize);
            wd=backend.end();
        }

        getIndex(backend.getOptions()).invalidate();
    }

    bool editRecord(AuthBackend &backend,AuthData &rec,int type){
        AuthData old(rec.Data.ruid);

        std::vector<AuthData::Record*> changemap; //use fieldname;
        std::vector<AuthData::Record*> newmap;

        getRecord(backend,old,type);

        for(AuthData *curdat=&rec; curdat; curdat=curdat->_next){
            bool newentry=true;
            if(curdat->Data.datasize==0)
                continue;

            for(AuthData *olddat=&old; olddat; olddat=olddat->_next){
                if(uuid::uuid(olddat->Data.ruid)==curdat->Data.ruid
                    && strcmp(olddat->Data.fieldname,curdat->Data.fieldname)==0
                ){
                    size_t end = olddat->Data.datasize;
                    if(end!=curdat->Data.datasize){
                        changemap.emplace_back(&curdat->Data);
                    }else{
                        for(size_t i=0; i<end; ++i){
                            if(olddat->Data.data[i]!=curdat->Data.data[i]){
                                changemap.emplace_back(&curdat->Data);
                                break;
                            }
                        }
                    }
                    newentry=false;
                    break;
                }
            }

            if(newentry){
                newmap.emplace_back(&curdat->Data);
            }

        }

        for(auto newel : newmap){
            backend.setPos(backend.end());
            backend.write((unsigned char*)newel,sizeof(AuthData::Record));;
            backend.write((unsigned char*)newel->data,newel->datasize);
        }

        const unsigned char zero = 0;


        for(auto chel : changemap){
            size_t rd=sizeof(authdb::AuthHeader),end=backend.end();
            while(rd+sizeof(AuthData::Record)<=end){
                std::shared_ptr<authdb::AuthData::Record> rdrec(new AuthData::Record);
                backend.setPos(rd);
                backend.read(reinterpret_cast<unsigned char*>(rdrec.get()),sizeof(AuthData::Record));
                if(uuid::uuid(rdrec->ruid)==rec.Data.ruid && rdrec->type != EmptyData && strcmp(rdrec->fieldname,chel->fieldname)==0){
                    backend.setPos(backend.end());
                    backend.write((const unsigned char*)chel,sizeof(AuthData::Record));
                    backend.write((const unsigned char*)chel->data,chel->datasize);
                    size_t dsize=rdrec->datasize;
                    memset(rdrec->ruid,0,sizeof(uuid::uuid_t));
                    rdrec->start = 0xFE;
                    rdrec->storage=StorageType::EmptyStorage;
                    rdrec->type=DataType::EmptyData;
                    memset(rdrec->fieldname,0,255);
                    rdrec->datasize=dsize;
                    backend.setPos(rd);
                    backend.write(reinterpret_cast<unsigned char*>(rdrec.get()),sizeof(AuthData::Record));
                    for(size_t i =0; i<rdrec->datasize; ++i){
                        backend.write(&zero,1);
                    }
                    rd=backend.getPos();
                }else{
                    size_t next=backend.getPos()+rdrec->datasize;
                    if(next<backend.getPos() || next>end) break;
                    rd=next;
                }
            }
        }

        if(!changemap.empty() || !newmap.empty()){
            getIndex(backend.getOptions()).invalidate();
            return true;
        }
        return false;
    }

    void delRecord(AuthBackend &backend,const uuid::uuid &uid,int type){
        if(uid.empty())
            throw AuthBackendError("user info uid required!");

        getIndex(backend.getOptions()).invalidate();

        size_t end=backend.end(),rd=sizeof(AuthHeader);

        while(rd+sizeof(AuthData::Record)<=end){
            std::shared_ptr<authdb::AuthData::Record> cur=std::make_shared<authdb::AuthData::Record>();
            backend.setPos(rd);
            size_t wr=backend.getPos();
            backend.read(reinterpret_cast<unsigned char*>(cur.get()),sizeof(AuthData::Record));
            size_t next=backend.getPos()+cur->datasize;
            if(next<backend.getPos() || next>end) break;
            rd=next;
            if(uid==cur->ruid && type == cur->type){
                std::cout << cur->datasize << std::endl;
                memset(cur->ruid,0,sizeof(uuid::uuid_t));
                cur->start = 0xFE;
                cur->storage=StorageType::EmptyStorage;
                cur->type=DataType::EmptyData;
                memset(cur->fieldname,0,255);
                backend.setPos(wr);
                backend.write(reinterpret_cast<unsigned char*>(cur.get()),sizeof(AuthData::Record));
                const unsigned char zero=0;
                for(size_t i=0; i<cur->datasize; ++i){
                    backend.write(&zero,1);
                }
            }
        }
    }

    void vacuumRecords(AuthBackend &backend){
        AuthBackend::Guard lck(backend);
        backend.vacuum();
    }
};
