func main(args)
{


    var dir = ".";
    if(args.Length > 1)
    {
        dir = args[1];
    }
    DB.Init(dir);

    const timer = new Timer(()=>{
        const time = DateTime.NowEpoch ?? 0;
        DB.Lock();
        const db = DB.Open();
        Sqlite.Exec(db, "DELETE FROM sessions WHERE expires < {time} AND expires > 0;");
        Sqlite.Close(db);
        DB.Unlock();
    },900000);

   
    
    /*
        PUT /api/v1/upload Authorization Bearer
        POST /api/v1/login Json object with email and password returns json object with either 200 for success {"token": "TOKEN_VAL"} or non 2XX if fails {"reason": "SOME ERROR"}
        POST /api/v1/logout use Authorization Bearer
        GET /api/v1/latest?name=PackageName returns 200 OK with json  {"version": "1.0.0.0-prod"} if it succeeds if it fails returns a failing status code with {"reason": "SOME ERROR"}
        GET /api/v1/download?name=PackageName&version=1.0.0.0-prod returns 200 OK with package bytes or 404 if doesn't exist
        GET /api/v1/search?q=SomeQuery&offset=&limit= returns 200 OK with json of packages {"packages": [{"name": "pkgName","version": "latestVersion", ...}]} or non success status code
        
    */

   
    Net.Http.ListenSimpleWithLoop((ctx)=>{
        if(ctx.Path == "/check_email")
        {
            ctx.WithMimeType("text/html").SendText(Pages.CheckEmail(ctx));
        }
        if(ctx.Path == "/reserved_prefixes")
        {
            ctx.WithMimeType("text/html").SendText(Pages.ReservedPrefixes(ctx));
            return true;
        }
        if(ctx.Path == "/admin")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Admin(ctx));
            return true;
        }
        if(ctx.Path == "/admin_register")
        {
            ctx.WithMimeType("text/html").SendText(Pages.AdminRegister(ctx));
            return true;
        }
        if(ctx.Path == "/admin_accounts")
        {
            ctx.WithMimeType("text/html").SendText(Pages.AdminAccount(ctx));
            return true;
        }
        if(ctx.Path == "/package")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            if(TypeOf(name) != "String") name = "";
            ctx.WithMimeType("text/html").SendText(Pages.Package(ctx,name));
            return true;
        }
        if(ctx.Path == "/package_docs")
        {
            ctx.WithMimeType("text/html").SendText(Pages.PackageDocs(ctx));
            return true;
        }
        if(ctx.Path == "/packages")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Packages(ctx));
            return true;
        }
        if(ctx.Path == "/api/v1/latest")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            if(TypeOf(name) != "String") name = "";
            var version = DB.GetLatestVersion(name);
            if(version != null)
            {
                ctx.WithMimeType("application/json").SendText(Json.Encode({version}));
                return true;
            }
        }
        if(ctx.Path == "/api/v1/search")
        {
            var filter = "";
            var type = ctx.QueryParams.TryGetFirst("type");
            if(TypeOf(type) != "String") type = "";
            var types = type.Length > 0 ? type.Split(",") : [];

            var pluginHost = ctx.QueryParams.TryGetFirst("pluginHost");
            if(TypeOf(pluginHost) == "String")
            {
                filter += $" AND v.pluginHost = {Sqlite.Escape(pluginHost)}";
            }
            if(types.Length > 0)
            {
                filter += " AND (";
                var first = true;
                each(var item : types)
                {
                    if(!first) {
                        filter += " OR ";
                    }
                    filter += $"v.type = {Sqlite.Escape(item)}";
                    first=false;
                }
                filter += ")";
            }


            var q = ctx.QueryParams.TryGetFirst("q");
            if(TypeOf(q) != "String") q = "";
            var offset = ParseLong(ctx.QueryParams.TryGetFirst("offset"));
            if(TypeOf(offset) != "Long") offset=0;
            var limit = ParseLong(ctx.QueryParams.TryGetFirst("limit"));
            if(TypeOf(limit) != "Long") limit = 20;
            if(limit <= 0) limit = 20;

            var res = DB.QueryPackages(q, offset*limit, limit, undefined, filter);
            if(TypeOf(res) != "List")
            {
                ctx.StatusCode=500;
                ctx.SendText("Packages not a list");
                return true;
            }
            else
            {
                ctx.WithMimeType("application/json").SendText(Json.Encode({packages=res}));
                return true;
            }
        }
        if(ctx.Path == "/change_motto")
        {
            if(ctx.Method == "POST")
            {
                var motto = ctx.QueryParams.TryGetFirst("motto");
                if(motto == null) motto = "";
                var session = DB.GetSession(ctx);
                if(session == null)
                {
                    ctx.StatusCode = 401;
                    ctx.SendText("<h1>You are not logged in</h1>");
                    return true;
                }

                var csrf = ctx.QueryParams.TryGetFirst("csrf");
                    var result = { Success=false, Reason = "Invalid CSRF"};

                if(DB.VerifyCSRF(session,csrf))
                {
                    var userId = DB.GetUserIdFromSession(session).accountId;
                    var url = DB.ChangeMotto(userId,motto);
                    ctx.StatusCode = 303;
                    ctx.ResponseHeaders.SetValue("Location", url);
                    ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href={url}>here</a> if it does not redirect
                    </null>);
                    return true;
                }
            }
        }
        if(ctx.Path == "/upload")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.Upload(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                if(ctx.NeedToParseFormData)
                {
                    var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
                    var hasFile=false;
                    var strm = FS.Local.OpenFile(filePath,"wb");
                    ctx.ParseFormData((mime,filename,name)=>{
                
                        if(name == "package")
                        {
                            if(hasFile) return new MemoryStream(true);
                            hasFile=true;
                    
                            return strm;
                        }
                        else
                            return new MemoryStream(true);
                    });
                    
                    strm.Close();
            
                    var session = DB.GetSession(ctx);
                    if(session == null)
                    {

                        if(FS.Local.FileExists(filePath))
                            FS.Local.DeleteFile(filePath);
                        ctx.StatusCode = 401;
                        ctx.SendText("<h1>You are not logged in</h1>");
                        return true;
                    }

                    var csrf = ctx.QueryParams.TryGetFirst("csrf");
                    var result = { Success=false, Reason = "Invalid CSRF"};

                    if(!DB.VerifyCSRF(session,csrf))
                    {
                        var userId = DB.GetUserIdFromSession(session).accountId;
                        result = DB.UploadPackage(userId, filePath);
                    }

                    if(FS.Local.FileExists(filePath))
                        FS.Local.DeleteFile(filePath);

                    if(result.Success)
                    {
                        ctx.StatusCode = 303;
                        ctx.ResponseHeaders.SetValue("Location", "/");
                        ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href="./">here</a> if it does not redirect
                        </null>);
                    }
                    else
                    {
                        ctx.StatusCode = 400;
                        ctx.SendText(result.Reason);
                    }
            
                return true;        
                }
            }

        }
        if(ctx.Path == "/api")
        {
            ctx.WithMimeType("text/html").SendText(Pages.API.Index());
            return true;
        }

        if(ctx.Path == "/api-v1")
        {
            ctx.WithMimeType("text/html").SendText(Pages.API.V1());
            return true;
        }
        if(ctx.Path == "/api/v1/versions")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            if(TypeOf(name) == "String")
            {
                var versions = DB.GetPackageVersions(name);
                ctx.WithMimeType("application/json").SendJson({
                    success=true,
                    versions
                });
                return true;
            }
            ctx.WithMimeType("application/json").SendJson({
                success=false
            });
            return true;
        }
        if(ctx.Path == "/delete_packages")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.DeletePackages(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                var packages = ctx.QueryParams.TryGetFirst("packages");
                var email = ctx.QueryParams.TryGetFirst("email");
                var password = ctx.QueryParams.TryGetFirst("password");
                var msg = DB.DeletePackages(email,password, packages);
                var html = <div class="container"><h1>{msg}</h1></div>;
                ctx.WithMimeType("text/html").SendText(Shell(msg,[], html));
            }
        }
        if(ctx.Path == "/api/v1/upload")
        {
            if(ctx.Method == "PUT")   
            {
                var session = DB.GetSessionFromBearer(ctx);
                if(session == null)
                {
                    ctx.StatusCode=401;
                    ctx.SendJson({
                        reason = "You are not logged in"
                    });
                    return true;
                }
                var userId = DB.GetUserIdFromSession(session).accountId;
                var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
                
                var strm = FS.Local.OpenFile(filePath,"wb");

                ctx.ReadStream(strm);
                strm.Close();
                var result = DB.UploadPackage(userId, filePath);
                if(result.Success)
                {
                    ctx.StatusCode = 204;
                    ctx.ResponseHeaders.SetValue("Content-Length","0");
                    ctx.WriteHeaders();
                    return true;
                }
                else {
                    ctx.StatusCode = 400;
                    ctx.SendJson({reason = result.Reason});
                    return true;
                }
            }
            else {
                ctx.StatusCode = 400;
                ctx.SendJson({
                    reason = $"Expected PUT method got {ctx.Method}"
                });
            }
            return true;
        }
        if(ctx.Path == "/api/v1/login")
        {
            if(ctx.Method == "POST")
            {
                var json = ctx.ReadJson();
                if(TypeOf(json) != "Dictionary" || TypeOf(json.email) != "String" || TypeOf(json.password) != "String") {
                    ctx.StatusCode = 400;
                    ctx.SendJson({
                        reason = "Expected a Json Dictionary, with the email and password"
                    });
                    return true;
                }

                var accountId = DB.GetAccountId(json.email, json.password);
                if(accountId == -1)
                {
                    ctx.StatusCode = 401;
                    
                    ctx.SendJson({
                        reason = "Invalid credentials"
                    });
                    return true;
                }



                ctx.SendJson({
                    token = DB.CreateSession(accountId,false, TypeIsString(json.name) ? json.name : "CrossLang Shell")
                });
                return true;
   

            }
            else {
                ctx.StatusCode = 400;
                ctx.SendJson({
                    reason = $"Expected POST method got {ctx.Method}"
                });
            }
            return true;
        }
        if(ctx.Path == "/api/v1/package_icon.png")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            var version = ctx.QueryParams.TryGetFirst("version");
            ctx.ResponseHeaders.SetValue("Content-Type", "image/png");
            ctx.SendBytes(DB.GetPackageIcon(name,version));
            return true;
        }
        if(ctx.Path == "/api/v1/download")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            var version = ctx.QueryParams.TryGetFirst("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)
                {
                    ctx.WithMimeType("application/crvm").WithContentDisposition($"{name}-{version}.crvm",false).SendStream(strm);
                    strm.Close();
                    return true;
                }
                
            }
            return false;
        }
        if(ctx.Path == "/api/v1/logout")
        {
            ctx.WithMimeType("application/json").SendText({
                Success=DB.DestroySession(DB.GetSessionFromBearer(ctx))
            }.ToString());
        }
        if(ctx.Path == "/logout")
        {
            if(DB.DestroySession(DB.GetSession(ctx)))
            {
                ctx.StatusCode = 302;
                ctx.ResponseHeaders.SetValue("Location", "/");
                ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href="./">here</a> if it does not redirect
                        </null>);
                return true;
            }
            else {
                ctx.WithMimeType("text/html").SendText(Shell("Not logged in",[],<h1>Not logged in</h1>));
                return true;
            }
        }
        if(ctx.Path == "/login")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.Login(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                
                var email = ctx.QueryParams.TryGetFirst("email");
                if(TypeOf(email) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the email buddy</h1>");
                    return true;
                }
                var password = ctx.QueryParams.TryGetFirst("password");
                if(TypeOf(password) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the password buddy</h1>");
                    return true;
                }

                var accountId = DB.GetAccountId(email, password);
                if(accountId == -1)
                {
                    ctx.StatusCode = 400;
                    ctx.SendText(Shell("Invalid credentials",[],"<h1>Invalid credentials</h1>"));
                    return true;
                }

                ctx.StatusCode = 303;
                ctx.ResponseHeaders.SetValue("Location", "/");
                const browser="Browser";
                const now = DateTime.NowEpoch??0;
                
                ctx.ResponseHeaders.SetValue("Set-Cookie", $"Session={DB.CreateSession(accountId,true, browser)}; SameSite=Strict; Expires={new DateTime(now+DB.Expires).ToHttpDate()}");
                ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href="./">here</a> if it does not redirect
                        </null>);
                return true;
            }
        }
        if(ctx.Path == "/verify")
        {
            var code = ctx.QueryParams.TryGetFirst("code");
            if(TypeOf(code) == "String")
            {
                var res = DB.VerifyEmail(code);
                if(res.Success)
                {
                    ctx.StatusCode = 302;
                    ctx.ResponseHeaders.SetValue("Location", "/");
                    ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href="./">here</a> if it does not redirect
                        </null>);
                    return true;
                }
                else {
                    ctx.StatusCode=401;
                    ctx.WithMimeType("text/html").SendText(Pages.VerificationFailed(res.Reason));
                    return true;
                }
            }
            else {
                ctx.WithMimeType("text/html").SendText(Pages.VerificationFailed("Requires query parameter code"));
                return true;
            }
        }
        if(ctx.Path == "/new_password")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.NewPassword(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {

                var code = ctx.QueryParams.TryGetFirst("code");
                var password = ctx.QueryParams.TryGetFirst("password");
                
                var confirm = ctx.QueryParams.TryGetFirst("confirm");
                if(TypeOf(code) == "String" && TypeOf(password) == "String" && TypeOf(confirm) == "String")
                {
                    var res = DB.UnforgetPassword(code,password,confirm);
                    if(res.Success)
                    {
                        ctx.StatusCode = 303;
                        ctx.ResponseHeaders.SetValue("Location", "/");
                        ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href="./">here</a> if it does not redirect
                        </null>);
                        return true;
                    }
                    else {
                        ctx.StatusCode=401;
                        ctx.WithMimeType("text/html").SendText(Pages.ResetPasswordFailed(res.Reason));
                        return true;
                    }
                }
                else {
                    ctx.StatusCode = 400;
                    ctx.WithMimeType("text/html").SendText(Pages.ResetPasswordFailed("Invalid State"));
                    return true;
                }
            }
        }
        if(ctx.Path == "/forgot_password")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.ForgotPassword(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                var email = ctx.QueryParams.TryGetFirst("email");   
                if(TypeOf(email) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the email buddy</h1>");
                    return true;
                }
                var res = DB.ForgotPassword(email);
                if(!res.Success)
                {
                    ctx.StatusCode = 400;
                    ctx.SendText($"<h1>Error: {Net.Http.HtmlEncode(res.Reason)}</h1>");
                    return true;
                }


                ctx.StatusCode = 303;
                ctx.ResponseHeaders.SetValue("Location", res.Redirect);
                ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href={res.Redirect}>here</a> if it does not redirect
                        </null>);
                return true;
            }
        }
        if(ctx.Path == "/signup")
        {
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Pages.Signup(ctx));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                var email = ctx.QueryParams.TryGetFirst("email");
                if(TypeOf(email) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the email buddy</h1>");
                    return true;
                }
                var displayName = ctx.QueryParams.TryGetFirst("displayName");
                if(TypeOf(displayName) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the displayName buddy</h1>");
                    return true;
                }
                var password = ctx.QueryParams.TryGetFirst("password");
                if(TypeOf(password) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the password buddy</h1>");
                    return true;
                }
                var passwordconfirm = ctx.QueryParams.TryGetFirst("passwordconfirm");
                if(TypeOf(passwordconfirm) != "String")
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>You forgot the passwordconfirm buddy</h1>");
                    return true;
                }

                if(password != passwordconfirm)
                {
                    ctx.StatusCode = 400;
                    ctx.SendText("<h1>The passwords do not match</h1>");
                    return true;
                }

                var res = DB.CreateUser(email, displayName, password);
                if(!res.Success)
                {
                    ctx.StatusCode = 400;
                    ctx.SendText(Shell("Error",[],$"<h1>Error: {Net.Http.HtmlEncode(res.Reason)}</h1>"));
                    return true;
                }


                ctx.StatusCode = 303;
                ctx.ResponseHeaders.SetValue("Location", res.Redirect);
                ctx.WithMimeType("text/html").SendText(<null>
                        <h1>Redirecting</h1>
                        Click <a href={res.Redirect}>here</a> if it does not redirect
                        </null>);
                return true;
            }
        }
        if(ctx.Path == "/account")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Account(ctx));
            return true;
        }
        if(ctx.Path == "/sessions")
        {

            ctx.WithMimeType("text/html").SendText(Pages.Sessions(ctx));
            return true;
        }
        if(ctx.Path == "/create_session")
        {
            ctx.WithMimeType("text/html").SendText(Pages.CreateSession(ctx));
            return true;
        }
        if(ctx.Path == "/session")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Session(ctx));
            return true;
        }
        if(ctx.Path == "/css/bootstrap.min.css")
        {
            ctx.WithMimeType("text/css").SendBytes(embed("css/bootstrap.min.css"));
            return true;
        }
        if(ctx.Path == "/css/bootstrap.min.css.map")
        {
            ctx.WithMimeType("application/json").SendBytes(embed("css/bootstrap.min.css.map"));
            return true;
        }

        if(ctx.Path == "/js/bootstrap.min.js")
        {
            ctx.WithMimeType("text/javascript").SendBytes(embed("js/bootstrap.min.js"));
            return true;
        }

        if(ctx.Path == "/js/bootstrap.min.js.map")
        {
            ctx.WithMimeType("application/json").SendBytes(embed("js/bootstrap.min.js.map"));
            return true;
        }
        if(ctx.Path == "/favicon.ico")
        {
            ctx.WithMimeType("image/x-icon").SendBytes(embed("favicon.ico"));
            return true;
        }
        if(ctx.Path == "/")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Index(ctx));
            return true;
        }
        
        return false;
    },DB.Port);

    timer.Callback = null;
}