func DB.DeletePackages(email,password, packageListString)
{
    if(TypeOf(email) != "String" || TypeOf(password) != "String")
    {
        return "Email or password not set in form";
    }
    if(TypeOf(packageListString) != "String")
    {
        return "Package List not set in form";
    }

    var accountId = DB.GetAccountId(email, password);
    
    var failed = "";

    func removeVersion(name, version)
    {
        var v = Version.Parse(version);
        if(name.Length == 0)
        {
            failed += "No package name\n";
            return;
        }
        if(v == null)
        {
            failed += "No package version\n";
            return;
        }

        DB.Lock();
        var db = DB.Open();
        var exec = Sqlite.Exec(db, $"SELECT * FROM packages WHERE packageName = {Sqlite.Escape(name)};");
        Sqlite.Close(db);
        DB.Unlock();

        if(exec.Length == 1)
        {
            var id = ParseLong(exec[0].id);
            if(ParseLong(exec[0].accountId) != accountId)
            {
                failed += $"User does not own package {name}\n";
                return;
            }
            DB.Lock();
            db = DB.Open();
            exec = Sqlite.Exec(db, $"SELECT * FROM versions WHERE packageId = {id};");
            var versionId = -1;
            var shallEraseEntirePackage = false;
            if(TypeOf(exec) == "List")
            {
                if(exec.Length == 1 && ParseLong(exec[0].version) == v.VersionInt)
                {
                    shallEraseEntirePackage=true;
                }
                else {
                    each(var item : exec)
                    {
                        if(ParseLong(item.version) == v.VersionInt)
                        {
                            versionId = ParseLong(item.id);
                            break;
                        }
                    }
                }
            }

            if(versionId > -1)
            {
                Sqlite.Exec(db, $"DELETE FROM versions WHERE id = {versionId};");
                FS.Local.DeleteFile(DB.working / "Packages" / name / $"{name}-{version}.crvm");
            }
            Sqlite.Close(db);
            DB.Unlock();
            if(shallEraseEntirePackage) {
                removePackage(name);
            }
            if(!shallEraseEntirePackage && versionId == -1)
            {
                failed += $"Failed to remove package {name}-{version}.crvm\n";
            }
        }
        else {
            failed += $"Could not find package {name}-{version}.crvm\n";
        }
    }

    func removePackage(name)
    {
        if(name.Length == 0)
        {
            failed += "No package name\n";
            return;
        }
        DB.Lock();
        var db = DB.Open();
        var exec = Sqlite.Exec(db, $"SELECT * FROM packages WHERE packageName = {Sqlite.Escape(name)};");
        Sqlite.Close(db);
        DB.Unlock();

        if(exec.Length == 1)
        {
            var id = ParseLong(exec[0].id);
            if(ParseLong(exec[0].accountId) != accountId)
            {
                failed += $"User does not own package {name}\n";
                return;
            }
            DB.Lock();
            db = DB.Open();
            exec = Sqlite.Exec(db, $"DELETE FROM versions WHERE packageId = {id};");
            exec = Sqlite.Exec(db, $"DELETE FROM packages WHERE id = {id};");
            Sqlite.Close(db);
            DB.Unlock();
            if(FS.Local.DirectoryExists(DB.working / "Packages" / name))
                FS.Local.DeleteDirectoryRecurse(DB.working / "Packages" / name);
        }
        else {
            failed += $"Could not find package {name}";
        }
    }
    

    each(var item : packageListString.Replace("\r","").Split("\n"))
    {
        var stageOrAstrisk = item.LastIndexOf('-');
        if(stageOrAstrisk > -1)
        {
            if(item[stageOrAstrisk+1] == '*')
            {
                removePackage(item.Substring(0, stageOrAstrisk));
            }
            else if(item[stageOrAstrisk+1] == 'd' || item[stageOrAstrisk+1] == 'a' || item[stageOrAstrisk+1] == 'b' || item[stageOrAstrisk+1] == 'p') {
                var versionIdx = item.LastIndexOf('-',stageOrAstrisk-1);
                if(versionIdx > -1)
                {
                    removeVersion(item.Substring(0,versionIdx),item.Substring(versionIdx+1));


                } else {
                    failed += $"{item}, Failed to find version part\n";
                }

            }   
            else {
                failed += $"{item}, Invalid version\n";
                continue;
            }
        }
        else {
            failed += $"{item}, No version at all\n";
        }
    }
    
    if(failed.Length > 0) return failed;

    return "Success";
}
func DB.CanUploadPackagePrefix(userId, packageName)
{
    var prefix = packageName.Split(".",true,2);
    if(prefix.Length >= 1)
    prefix = prefix[0];
    else return false;
    DB.Lock();
    var dbCon = DB.Open();
    var exec = Sqlite.Exec(dbCon, $"SELECT * FROM reserved_prefixes WHERE prefix = {Sqlite.Escape(prefix)};");
    
    Sqlite.Close(dbCon);
    DB.Unlock();

    if(TypeOf(exec) == "List" && exec.Length > 0) return ParseLong(exec[0].accountId) == userId;

    return true;
}


func DB.PackageExists(userId,pkgInfo)
{
    var statusCode = 4;
    DB.Lock();
    var dbCon = DB.Open();
    var exec = Sqlite.Exec(dbCon,$"SELECT * from packages WHERE packageName = {Sqlite.Escape(pkgInfo.Name)};");
    if(TypeOf(exec) == "List")
    {
        if(exec.Length == 1)
        {
            var pkgId = exec[0].id;
            if(ParseLong(exec[0].accountId) != userId)
                statusCode=4;
            else 
            {
                exec = Sqlite.Exec(dbCon,$"SELECT * from versions WHERE packageId = {pkgId} AND version = {pkgInfo.Version.VersionInt};");
                if(TypeOf(exec) == "List")
                {
                    if(exec.Length == 1)
                    {
                        if(pkgInfo.Version.Stage == "dev")
                            statusCode=3;
                        else
                            statusCode=2;
                    }
                    else statusCode=1;
                }
            }
        }
        else statusCode=0;
    }

    Sqlite.Close(dbCon);
    DB.Unlock();
    return statusCode;
}
func DB.AddPackage(userId,pkgInfo)
{
    DB.Lock();
    var dbCon = DB.Open();
    Sqlite.Exec(dbCon,$"INSERT INTO packages (packageName, accountId) VALUES ({Sqlite.Escape(pkgInfo.Name)}, {userId});");
    Sqlite.Close(dbCon);
    DB.Unlock();
    
}
func DB.GetPackageFileData(name, version)
{
    if(TypeOf(name) != "String") name = "";
    if(TypeOf(version) != "String") version = "";
    
    var file =  DB.working / "Packages" / name / $"{name}-{version}.crvm";
   
    
    if(FS.Local.FileExists(file) && name.Length > 0 && version.Length > 0)
    {
        var strm = FS.Local.OpenFile(file,"rb");
        
        if(strm != null)
        {
            var exec = VM.LoadExecutable(strm);
            strm.Close();
            return exec;
        }
    }
    return null;
}
func DB.GetPackageIcon(name, version)
{
    var file = DB.GetPackageFileData(name, version);
    if(file != null)
    {
        if(file.Icon != null)
        {
            return file.Icon;
        }
    }
    
    return embed("crosslang.png");
}
func DB.UploadPackage(userId, filePath)
{
    if(!FS.Local.FileExists(filePath)) return {Success=false, Reason = "File does not exist"};
    var userInfo = DB.GetUserInfo(userId);
    if(userInfo == null) return { Success=false, Reason = "User does not exist"};
  
    if((userInfo.flags & DB.FLAG_VERIFIED) == 0) return { Success=false, Reason = "User is not verified"};

    var strm = FS.Local.OpenFile(filePath,"rb");
    var failed=false;
    var reason = "";
    try {
        var pkgInfo = VM.LoadExecutable(strm);
    }catch(ex) {
        if(ex.Type == "NativeException")
            reason = ex.Text;

            failed=true;
    }
    
    strm.Close();
    if(failed) return {Success=false, Reason = reason};
    if(!DB.CanUploadPackagePrefix(userId,pkgInfo.Name)) return { Success = false, Reason = "You can't upload a package with that prefix."};
    
    switch(DB.PackageExists(userId, pkgInfo))
    {
        case 0:
            //package name does not exist
            DB.AddPackage(userId,pkgInfo);
         case 1:
            DB.AddVersion(pkgInfo);
            FS.Local.CreateDirectory(DB.working / "Packages" / pkgInfo.Name);
       
            FS.Local.MoveFile(filePath, DB.working / "Packages" / pkgInfo.Name / $"{pkgInfo.Name}-{pkgInfo.Version}.crvm");
      
            
            //package version does not exist
           break;
        case 2:
            return { Success = false, Reason = "Package already exists and is not a dev package." };
        case 3:
            //update package version (it exists but is dev)
            
            DB.UpdateVersion(pkgInfo);
            FS.Local.MoveFile(filePath, DB.working / "Packages" / pkgInfo.Name / $"{pkgInfo.Name}-{pkgInfo.Version}.crvm");
            break;
        case 4:
            //package is not yours
            return { Success = false, Reason = "You don't own the package."};
    }

    return {Success=true};
}
func DB.QueryPackages(q, offset, limit,$account,$filter)
{
    if(TypeOf(offset) != "Long") offset = 0;
    if(TypeOf(limit) != "Long") limit = 20;
    if(TypeOf(filter) != "String") var filter = "";
    var q2 = Sqlite.Escape($"%{q}%");

    var accountId = -1;

    if(TypeOf(account) == "String")
    {
        DB.Lock();
        var dbCon = DB.Open();
        var res = Sqlite.Exec(dbCon, $"SELECT * FROM accounts WHERE accountName = {Sqlite.Escape(account)};");
        Sqlite.Close(dbCon);
        DB.Unlock();
        if(res.Length > 0)
        {
            accountId = ParseLong(res[0].id);
        }
        else {
            accountId = 0;
        }
    }

    var sql = accountId > -1 ? $"SELECT * FROM packages p inner join versions v on p.id = v.packageId{filter} and (p.packageName LIKE {q2} OR v.description LIKE {q2}) and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId) INNER JOIN accounts a on a.id = p.accountId WHERE p.accountId == {accountId}  LIMIT {limit} OFFSET {offset};" :$"SELECT * FROM packages p inner join versions v on p.id = v.packageId{filter} and (p.packageName LIKE {q2} OR v.description LIKE {q2}) and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId) INNER JOIN accounts a on a.id = p.accountId  LIMIT {limit} OFFSET {offset};";
    DB.Lock();
    var dbCon = DB.Open();
    var res = Sqlite.Exec(dbCon, sql);
    Sqlite.Close(dbCon);
    DB.Unlock();

    var res2=[];
    each(var item : res)
    {
        var uploadTime = ParseLong(item.uploadTime);
        res2.Add({
            accountName = item.accountName,
            description = item.description,
            homepage = item.homepage,
            license = item.license,
            maintainer = item.maintainer,
            packageName = item.packageName,
            uploadTime,
            uploadDate = new DateTime(uploadTime).ToString("%Y/%m/%d %H:%M:%S UTC"),
            repo = item.repo,
            type = item.type,
            version = Version.FromLong(ParseLong(item.version)).ToString()
        });
    }
    return res2;
}

func DB.GetPackages($page)
{
    if(TypeOf(page) != "Long") page = 1;

    var sql = "SELECT * FROM packages p join versions v on p.id = v.packageId and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId);";
    DB.Lock();
    var dbCon = DB.Open();
    var res = Sqlite.Exec(dbCon, sql);
    

    Sqlite.Close(dbCon);
    DB.Unlock();
    var res2 = [];
    each(var item : res)
    {
        res2.Add({
            id = item.packageId,
            name = item.packageName,
            version = Version.FromLong(ParseLong(item.version)).ToString(),
            homepage = item.homepage,
            repo = item.repo,
            type = item.type,
            maintainer = item.maintainer,
            description = item.description
        });
    }
    return res2;
}
func DB.RemovePrefix(userName, prefix)
{

    if(TypeOf(prefix) != "String" || prefix.Length == 0) return {Success=false, Reason="Invalid prefix"};
    var user = DB.GetAccountInfo(userName);
    if(TypeOf(user) != "Dictionary") return {Success=false, Reason = "Username not found"};
    if((user.flags & DB.FLAG_VERIFIED) == 0) return {Sucess=false, Reason="Account is not verified"};
    
    DB.Lock();

    var dbCon = DB.Open();
    var exec = Sqlite.Exec(dbCon,$"DELETE FROM reserved_prefixes WHERE accountId == {user.id} AND prefix == {Sqlite.Escape(prefix)};");

   
    Sqlite.Close(dbCon);
    DB.Unlock();

    if(TypeOf(exec) == "String") return {Success = false, Reason = exec};

    return {Success=true};
}
func DB.CreatePrefix(userName, prefix)
{
    if(TypeOf(prefix) != "String" || prefix.Length == 0) return {Success=false, Reason="Invalid prefix"};
    var user = DB.GetAccountInfo(userName);
    if(TypeOf(user) != "Dictionary") return {Success=false, Reason = "Username not found"};
    if((user.flags & DB.FLAG_VERIFIED) == 0) return {Sucess=false, Reason="Account is not verified"};
    
    DB.Lock();

    var dbCon = DB.Open();
    var exec = Sqlite.Exec(dbCon,$"INSERT INTO reserved_prefixes (accountId,prefix) values ({user.id},{Sqlite.Escape(prefix)});");

   
    Sqlite.Close(dbCon);
    DB.Unlock();

    if(TypeOf(exec) == "String") return {Success = false, Reason = exec};

    return {Success=true};
}

func DB.QueryReservedPrefixes(name)
{
    var user = DB.GetAccountInfo(name);
    if(TypeOf(user) != "Dictionary") return {Success=false, Reason = "Username not found"};

    DB.Lock();

    var dbCon = DB.Open();
    var exec = Sqlite.Exec(dbCon, $"SELECT * FROM reserved_prefixes WHERE accountId == {user.id};");
    
    Sqlite.Close(dbCon);

    DB.Unlock();
    if(TypeOf(exec) == "String") return {
        Success=false,
        Reason=exec
    };

    var items = [];
    each(var item : exec)
    {
        items.Add(item.prefix);
    }

    return {
        Success=true,
        Items = items
    };
}

func DB.DownloadPackage(log,userId,server, name, version)
{
    Console.WriteLine("INDLPKG");
    if(!DB.CanUploadPackagePrefix(userId,name)) return;

    var res = DB.PackageExists(userId, {Name = name, Version = Version.Parse(version)});
    if(res == 2 || res == 4) return;

    const resp=Net.Http.MakeRequest($"{server.TrimEnd('/')}/api/v1/download?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}",{FollowRedirects=true});
    if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
    {
        var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
                
        var strm = FS.Local.OpenFile(filePath,"wb");
        resp.CopyToStream(strm);
        strm.Close();
        resp.Close();

        //we need to load the exec, because the main thing erases it
        strm = FS.Local.OpenFile(filePath,"rb");
        const exec = VM.LoadExecutable(strm);
        strm.Close();
        const status = DB.UploadPackage(userId, filePath);
        if(status.Success)
        {
            each(var item in exec.Dependencies)
            {
                DB.DownloadPackage(log, userId, server, item.Name, item.Version.ToString());
            }
            each(var item in exec.Tools)
            {
                DB.DownloadPackage(log, userId, server, item.Name, item.Version.ToString());
            }
        }
        else {
            log($"Failed to add package: {name} with {version}");
        }
    }
}
