function createCanvas(id) {
    var tempCanvas = document.createElement("canvas");
    tempCanvas.id = id;
    tempCanvas.width = canvases.cvs.width;
    tempCanvas.height = canvases.cvs.height;
    tempCanvas.style = "image-rendering:pixelated;image-rendering: crisp-edges;display:none";//display:none;

    document.body.appendChild(tempCanvas);

    canvases[`${id}cvs`] = document.getElementById(id);
    canvases[`${id}ctx`] = canvases[`${id}cvs`].getContext("2d");
}

function startLoops() {
    try {draw} catch (err){console.warn(bug+" no draw function found");return null;}
    try {update} catch (err){console.warn(bug+" no update function found");return null;}
    try {input} catch (err){seperateInputLoop=false;}
    onAssetsLoaded();

    requestAnimationFrame(drawLoop);
    setInterval(updateLoop,1000/updateFPS);

    if(seperateInputLoop) {
        setInterval(inputLoop,4);
    }
}

function mousePosition() {
    if(drawMode===0) {
        return {x:(mousePos.x)-camera.x,y:(mousePos.y)-camera.y};
    } else if(drawMode===1) {
        var xoff = canvases.cvs.width/2;
        var yoff = canvases.cvs.height/2;
        return {x:((mousePos.x-xoff)/camera.zoom+xoff)-camera.x,y:((mousePos.y-yoff)/camera.zoom+yoff)-camera.y};
    } else {
        var xoff = canvases.cvs.width/2;
        var yoff = canvases.cvs.height/2;
        var tempPos = {x:((mousePos.x-xoff)/camera.zoom+xoff)-camera.x,y:((mousePos.y-yoff)/camera.zoom+yoff)-camera.y};

        var center = {x:-camera.x + cw/2, y:-camera.y + ch/2};
        var tempAngle = pointTo(center,tempPos) - camera.angle; 
        var tempDist = dist(center,tempPos);

        return {x:center.x + (Math.cos(tempAngle) * tempDist),y:center.y + (Math.sin(tempAngle) * tempDist)}
    }
}

function addStyle() {
    var tempStyle = document.createElement("style");
    tempStyle.id="gamejsstyle";
    document.head.appendChild(tempStyle);
    var tempMeta = document.createElement("meta");
    tempMeta.setAttribute("charset","utf-8");
    document.head.appendChild(tempMeta);
}

function rand(min,max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function radToDeg(rad) {return rad / Math.PI * 180;}
function degToRad(deg) {return deg * Math.PI / 180;}

function velocity(angle) {
    return {x:Math.sin(angle),y:Math.cos(angle)};
}

function pointTo(point,targetPoint) {
    var adjacent = (targetPoint.x - point.x);
    var opposite = (targetPoint.y - point.y);
    var h = Math.atan2(opposite, adjacent);
    return h;
}

function loadImagesAndSounds() {
    var curpath="";
    context = new AudioContext();
    sfxVolumeNode = context.createGain();
    sfxVolumeNode.connect(context.destination);
    bmgVolumeNode = context.createGain();
    bmgVolumeNode.connect(context.destination);
    deeper(images,"image");
    deeper(audio,"sound");
    function deeper(curpos,type) {
        let addedPath="";
        for(let j=0;j<curpos.length;j++) {
            if(typeof curpos[j]=="string") {
                if(j==0) {
                    curpath+=curpos[j];
                    addedPath = curpos[j];
                } else {
                    if(type=="image") {
                        let name = curpath + curpos[j];
                        imagePaths.push(name);
                        let temp = new Image();
                        temp.src = name;
                        temp.onerror = function () {
                            console.warn(bug+" "+this.src + " was not found");
                        };
                        temp.onload = function() {spriteLoad(name,temp);}
                        imgs.push(temp);
                    } else if(type=="sound") {
                        audioPaths.push(curpath + curpos[j]);
                        newSound(curpath + curpos[j]);
                    }
                }
            }
            if(typeof curpos[j]=="object") {
               deeper(curpos[j],type);
            }
        }
        curpath = curpath.slice(0,curpath.length-addedPath.length);
    }
    
    loadingCircle = new Image();
    loadingCircle.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAf0lEQVQ4jc2SuxHAIAxDbY4pMpHmyJSeKGuQKpyj2Hy6qETPQvhQCWRmDRfeZ4cJAGW28mAUyL4Pqmx2nfK+zaR59glRHo5qZi0BaPHmbDhiyuzdsza9wcrtEVtG4Ip+FLCzTM+WneWxPv9gpQUzmhncLPOHUCYfHr4/C4r2dQPfhkeIbjeYWgAAAABJRU5ErkJggg==";
    clickSound = new Audio("data:audio/x-wav;base64,UklGRowBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YWgBAADa/6T/2/+S/x//pP769Xr4fPh5+H34evh7+Pv6gf18/QIAhQcIDxUZFR4VHhgeEx6TFgkPCgqAAnz49/X18HLu7ubo4eXc5dzj3Gjf5+Fr5G7pce759YECiwwRFBQZlxuXG5cbFRmTFo8RCAoAAPz69/X08G7udO5s7vz1dvj++nv9gP3+/wEAgwKBAogHkBEpLUNG1lzqcPV683r4eu51ZmnVV7w+qypy88fDoKAXlAqKBoUAgIeHlpYsrTu87Ot9/ZIRGxkoKDEtqionIxgZiwfj5lnaz9JN0E3QV9pd3+Tm9fUBAAQFBwWNDBEPFxSYFpsWEA9+/fP1dfNt83bz8fX9+gQFkAwPDxQPEQ+IBwIAdfjv8OLm2+HX3Nrh4+bm63n4BAUUDx0ZqyCrJSkelBGEAnb4a+5a5Njcztxc31jkZunx9QgFGhQfGa0gqCCuIKkgpRsaFIsHdfhj6dfh0NzM19Th6es=");
    let pos = {x:cw/2-100,y:ch/2-100};
    optionsButtons.screenSize = {x:pos.x+160,y:pos.y+12,w:50,h:20};
    optionsButtons.sfx = {x:pos.x+125,y:pos.y+40,w:120,h:20};
    optionsButtons.bmg = {x:pos.x+125,y:pos.y+70,w:120,h:20};
    loadLoop();
}

function loadLoop() {
    if(Object.keys(sprites).length == imagePaths.length && audioPaths.length == audioLoadedLength) {
        startLoops();
        imagePaths=[];
        audioPaths=[];
        imgs=[];
    } else {
        curCtx.fillStyle="#2d2d2d";
        curCtx.fillRect(0,0,cw,ch);
        text(`audio:   ${audioLoadedLength}/${audioPaths.length}`,10,30,"white",2);
        text(`sprites: ${Object.keys(sprites).length}/${imagePaths.length}`,10,10,"white",2);
        curCtx.setTransform(1, 0, 0, 1, Math.round(cw/2), Math.round(ch/2));
        curCtx.rotate(loadAng);
        loadAng+=0.1;
        curCtx.drawImage(loadingCircle,Math.round(-8),Math.round(-8));
        curCtx.setTransform(1, 0, 0, 1, 0, 0);
        requestAnimationFrame(loadLoop);
    }
}

function spriteLoad(path,image) {
    let startpos;
    let endpos = path.lastIndexOf(".");
    for(let j=endpos-1;acceptableChars.includes(path[j]);j--) {startpos=j;}
    let spriteName = path.slice(startpos,endpos)
    let dsize=Math.max(image.width,image.height)/2;
    sprites[spriteName] = {spr:image,drawLimitSize:dsize};
    
}

function newSound(src) {
    let startpos;
    let endpos = src.lastIndexOf(".");
    for(let j=endpos-1;acceptableChars.includes(src[j]);j--) {startpos=j;}
    let soundName = src.slice(startpos,endpos); 
    sounds[soundName] = {nodes:[],volNodes:[],src:src,type:"sfx",volume:1};
    sounds[soundName].nodes = [1];

    let loadingSound = new Audio();
    loadingSound.onerror = function () {
        console.warn(bug+" "+ src + " was not found");
    };
    loadingSound.src = src;
    loadingSound.preload='auto';
    loadingSound.addEventListener('canplaythrough', function() { 
        audioLoadedLength++;
     }, false);
    sounds[soundName].nodes.push(loadingSound);

    let soundNode = context.createMediaElementSource(loadingSound);
    let gainNode = context.createGain();

    soundNode.connect(gainNode);
    gainNode.connect(sfxVolumeNode);

    abuffer.push(soundNode);
    volumeList.push(gainNode);
    sounds[soundName].volNodes.push(volumeList.length-1);
}

function addSound(sound) {
    let loadingSound = new Audio();
    loadingSound.src = sound.src;
    loadingSound.preload='auto';
    sound.nodes.splice(sound.nodes[0],0,loadingSound);

    let soundNode = context.createMediaElementSource(loadingSound);
    let gainNode = context.createGain();
    gainNode.gain.value=sound.volume;

    soundNode.connect(gainNode);
    gainNode.connect(sound.type=="sfx"?sfxVolumeNode:bmgVolumeNode);

    abuffer.push(soundNode);
    volumeList.push(gainNode);
    sound.volNodes.push(volumeList.length-1);

    volumeList[sound.volNodes[sound.volNodes.length-1]].gain.value = volumeList[sound.volNodes[0]].gain.value
}



function play(sound) {
    s=sound.nodes;
    if(s[s[0]].ended || !(s[s[0]].played.length)) {
        s[s[0]].play();
        s[0]++;
        if(s[0]==s.length) {
            s[0]=1;
        }
    } else {
        addSound(sound);
        s[s[0]].play();
        s[0]++;
        if(s[0]==s.length) {
            s[0]=1;
        }
    }
}

function setVolume(sound,volume) {
    for(let i=0,l=sound.volNodes.length;i<l;i++) {
        volumeList[sound.volNodes[i]].gain.value = volume;
    }
}

function setType(sound,newType) {
    for(let i=0,l=sound.volNodes.length;i<l;i++) {
        volumeList[sound.volNodes[i]].disconnect(sound.type=="sfx"?sfxVolumeNode:bmgVolumeNode);
        volumeList[sound.volNodes[i]].connect(newType=="sfx"?sfxVolumeNode:bmgVolumeNode);
    }
    sound.type = newType;
}

function stop(sound) {
    s=sound.nodes;
    for(let i=1;i<s.length;i++) {
        s[i].pause();
        s[i].currentTime = 0;
    }
}

function handleOptionsInput() {
    let ImTierdMakemenuwork=true;
    if(optionsMenu) {
        if(mousePress[0]) {
            if(rectpoint(optionsButtons.screenSize,mousePos)) {
                if(screenSize=="1:1") {
                    screenSize = "fit";
                    canvasScale=0;
                } else {
                    screenSize = "1:1";
                    canvasScale=1;
                }
            }
            if(!rectpoint({x:cw/2,y:ch/2,w:200,h:200},mousePos)) {
                optionsMenu=false;
                ImTierdMakemenuwork=false;
            }
        }
        if(mouseDown[0]) {
            if(rectpoint(optionsButtons.sfx,mousePos)) {
                volume.sfx = (mousePos.x-(optionsButtons.sfx.x-60))/120;
            }
            if(rectpoint(optionsButtons.bmg,mousePos)) {
                volume.bgm = (mousePos.x-(optionsButtons.bmg.x-60))/120;
            }
        }
    }
    if(mousePos.x>cw-32&&mousePos.y<32) {
        if(mousePress[0]&&ImTierdMakemenuwork) {
            clickSound.play();
            paused=true;
            optionsMenu=!optionsMenu;
        }
        optionsHover = 25;
    } else {
        optionsHover = 0;
    }
    if(mousePos.x<cw-32&&mousePos.x>cw-64&&mousePos.y<32) {
        pauseHover = 25;
        if(mousePress[0]) {
            clickSound.play();
            paused=!paused;
        }
    } else {
        pauseHover = 0;
    }
    
}
function addFont() {
    var tempStyle = document.createElement("style");
    tempStyle.innerHTML = `
    @font-face {
        font-family: 'PixelArial11';
        src: url("./pixelmix.ttf") format('truetype');
        font-weight: 900;
        font-style: normal;
    
    }
    html {font-family: 'PixelArial11' !important; font-size: 16px;}
    `;
    document.head.appendChild(tempStyle);
    canvases.ctx.textBaseline = "hanging";
    canvases.ctx.textAlign = "left";
}
var scaleDefault = 1;
function img(img,x,y,angle=0,sx=scaleDefault,sy=scaleDefault) {
    var half = img.drawLimitSize;
    if((x+half>drawLimitLeft&&x-half<drawLimitRight&&y+half>drawLimitTop&&y-half<drawLimitBottom)||absDraw) {
        let spr = img.spr;
        if(angle===0&&sx===1&&sy===1) {
            curCtx.drawImage(spr,Math.round(x+camera.x+difx-(spr.width/2)),Math.round(y+camera.y+dify-(spr.height/2)));
        } else {
            curCtx.setTransform(sx, 0, 0, sy, Math.round(x+camera.x+difx), Math.round(y+camera.y+dify));
            curCtx.rotate(angle);
            curCtx.drawImage(spr,Math.round(-spr.width/2),Math.round(-spr.height/2));
            curCtx.setTransform(1, 0, 0, 1, 0, 0);
        }
    }
}

function imgIgnoreCutoff(img,x,y,angle=0,sx=1,sy=1) {
    let spr = img.spr;
    if(angle===0&&sx===1&&sy===1) {
        curCtx.drawImage(spr,Math.round(x+camera.x+difx-(spr.width/2)),Math.round(y+camera.y+dify-(spr.height/2)));
    } else {
        curCtx.setTransform(sx, 0, 0, sy, Math.round(x+camera.x+difx), Math.round(y+camera.y+dify));
        curCtx.rotate(angle);
        curCtx.drawImage(spr,Math.round(-spr.width/2),Math.round(-spr.height/2));
        curCtx.setTransform(1, 0, 0, 1, 0, 0);
    }
}

function rect(x,y,w,h,color) {
    curCtx.fillStyle = color;
    curCtx.fillRect(x-(w/2)+camera.x+difx,y-(h/2)+camera.y+dify,w,h);
}

function cartesianRect(x,y,w,h,color) {
    curCtx.fillStyle = color;
    curCtx.fillRect(x+camera.x+difx,y+camera.y+dify,w,h);
}

function circle(x,y,r,color) {
    curCtx.beginPath();
    curCtx.arc(x+camera.x+difx, y+camera.y+dify, r, 0, 2 * Math.PI, false);
    curCtx.fillStyle = color;
    curCtx.fill();
}

function line(x1, y1, x2, y2, weight, color) {
    curCtx.beginPath();
    curCtx.strokeStyle = color;
    curCtx.lineWidth = weight;
    curCtx.moveTo(x1 + camera.x + difx, y1 + camera.y + dify);
    curCtx.lineTo(x2  + camera.x + difx , y2 + camera.y + dify);
    curCtx.stroke();
}

function shape(x,y,relitivePoints,color) {
    x+=camera.x+difx;
    y+=camera.y+dify;
    curCtx.fillStyle = color;
    curCtx.beginPath();
    curCtx.moveTo(x+relitivePoints[0].x, y+relitivePoints[0].y);
    for(let i=1,l=relitivePoints.length;i<l;i++) {
        curCtx.lineTo(x+relitivePoints[i].x, y+relitivePoints[i].y);
    }
    curCtx.fill();
}

function text(txt,x,y,color="black",size=1,maxWidth=cw) {
    txt = txt.toString();
    curCtx.fillStyle = color;
    curCtx.font = `${Math.round(size)*8}px PixelArial11`;
                                                                                        //I hate text wrapping now 
    var txtList = txt.split("\n");                                                      //split string on enters
    for(let i=0;i<txtList.length;i++) {                                                 //go through array of strings
        if(curCtx.measureText(txtList[i]).width>maxWidth) {                             //if the string is too big, divide up into smaller strings
            var tempTxt = txtList[i].split(" ");                                        //split into individual words
            var tempStr="";                                                             //string for measuring size
            var addAmount=0;                                                            //track where in the txtList we are
            txtList.splice(i,1);                                                        //remove the too long string
            for(let j=0;j<tempTxt.length;j++) {                                         //go through the split up string
                if(curCtx.measureText(tempStr + tempTxt[j] + " ").width<maxWidth) {     //if adding a word doesn't make tempStr too long, add it, other wise, add tempStr to txtList;
                    tempStr += tempTxt[j] + " ";
                } else {
                    if(j==0) {tempStr+=tempTxt[j];}                                     //if we are here when j is 0, we have one word that is longer then the maxWidth, so we just draw it
                    txtList.splice(i+addAmount,0,tempStr);                              //put tempStr in txtList
                    addAmount++;                                                        //move the position we put the tempStr in
                    tempStr="";                                                         //reset tempStr
                    tempTxt.splice(0,(j==0?1:j));                                       //delete words that have been used
                    j=-1;                                                               //make it so in the next loop, j starts at 0
                }
            }
            if(tempStr.length!=0) {
                txtList.splice(i+addAmount,0,tempStr);                                  //add any leftover text
            }
        }
    }

    for(let i=0;i<txtList.length;i++) {
        curCtx.fillText(txtList[i],x+camera.x+difx,y+camera.y+dify+((i+(drawMode?1:0))*8*size+(size*i)));
    }
}

function textWidth(txt,size=1) {
    txt = txt.toString();
    curCtx.font = `${Math.round(size)*8}px PixelArial11`;
    return curCtx.measureText(txt).width;
}

function centerCameraOn(x,y) {
    camera.x = -x+canvases.cvs.width/2;
    camera.y = -y+canvases.cvs.height/2;
}

function moveCamera(x,y) {
    camera.x -= y * Math.sin(camera.angle);
    camera.y -= y * Math.cos(camera.angle);
    camera.x -= x * Math.sin(camera.angle + 1.57079632);
    camera.y -= x * Math.cos(camera.angle + 1.57079632);
}

function imgRotScale(x,y,angle,scale,pic,ctx) { //used for camera movement
    ctx.setTransform(scale, 0, 0, scale, x, y);
    ctx.rotate(angle);
    ctx.drawImage(pic,-pic.width/2,-pic.height/2);
    ctx.setTransform(1, 0, 0, 1, 0, 0);
}

function drawCursor() {
    if(cursor.sprite&&mouseOnCanvas) {
        if(cursor.alignment) {
            canvases.ctx.drawImage(cursor.sprite.spr,mousePos.x-Math.round(cursor.sprite.spr.width/2),mousePos.y-Math.round(cursor.sprite.spr.height/2));
        } else {
            canvases.ctx.drawImage(cursor.sprite.spr,mousePos.x,mousePos.y);
        }
        cursor.show = false;
    } else {
        cursor.show = true;
    }
}

function render() {
    if(drawMode===1) {
        imgRotScale(canvases.cvs.width/2,canvases.cvs.height/2,0,camera.zoom,canvases.buffer1cvs,canvases.ctx);
    }
    if(drawMode===2) {
        imgRotScale(canvases.cvs.width/2,canvases.cvs.height/2,camera.angle,1,canvases.buffer2cvs,canvases.buffer1ctx);
        imgRotScale(canvases.cvs.width/2,canvases.cvs.height/2,0,camera.zoom,canvases.buffer1cvs,canvases.ctx);
    }
}

function clearCanvases() {
    canvases.ctx.clearRect(0,0,canvases.cvs.width,canvases.cvs.height);
    canvases.buffer1ctx.clearRect(0,0,canvases.buffer1cvs.width,canvases.buffer1cvs.height);
    canvases.buffer2ctx.clearRect(0,0,canvases.buffer2cvs.width,canvases.buffer2cvs.height);
}

function switchDrawMode() {
    if(camera.zoom<1) {camera.zoom=1;}
    if(camera.angle!=0) {
        drawMode=2;
    } else if(camera.zoom!=1) {
        drawMode=1;
    } else {
        drawMode=0;
    }
    switch (drawMode) {
        case 0: curCtx = canvases.ctx; break;
        case 1: curCtx = canvases.buffer1ctx; break;
        case 2: curCtx = canvases.buffer2ctx; break;
    }
}

function resizeBuffers() {
    var tempSize = maxCvsSize/camera.zoom;
    var tempSizeAndPadding = tempSize + (tempSize/2)

    canvases.buffer2cvs.width = tempSizeAndPadding;
    canvases.buffer2cvs.height = tempSizeAndPadding;
    
    if(drawMode===2) {
        difx = (canvases.buffer2cvs.width - canvases.cvs.width)/2;
        dify = (canvases.buffer2cvs.height - canvases.cvs.height)/2;
    } else {
        difx=0;
        dify=0;
    }
    canvases.buffer2ctx.imageSmoothingEnabled = false;
}

function scaleCanvases() { //scales canvas by canvas scale, if scale is 0, canvas will try to fit screen
    var style = document.getElementById("gamejsstyle");
    if(canvasScale==0) {
        var tempScale = Math.min(Math.floor(window.innerWidth/canvases.cvs.width),Math.floor(window.innerHeight/canvases.cvs.height));
        tempScale=tempScale<1?1:tempScale;
        autoScale=tempScale;
        style.innerHTML = `#game {image-rendering:pixelated;image-rendering: crisp-edges;width:${tempScale*canvases.cvs.width}px;cursor: ${cursor.show?"crosshair":"none"};}`;
    } else {
        style.innerHTML = `#game {image-rendering:pixelated;image-rendering: crisp-edges;width:${Math.floor(canvasScale*canvases.cvs.width)}px;cursor: ${cursor.show?"crosshair":"none"};}`;
    }
}

function drawButtons() {
    let pos = {x:cw-16,y:16}; 
    //options
    rect(pos.x,pos.y,34,34,"#9c9c9c");
    let c = optionsHover+45;
    rect(pos.x,pos.y,32,32,`rgb(${c},${c},${c})`);
    c = optionsHover + 69;
    let cc = `rgb(${c},${c},${c})`;
    rect(pos.x,pos.y-6,26,4,cc);
        rect(pos.x-6,pos.y-6,4,8,cc);
    rect(pos.x,pos.y+6,26,4,cc);
        rect(pos.x+11,pos.y+6,4,8,cc);
    //pause
    pos.x-=33;
    rect(pos.x,pos.y,34,34,"#9c9c9c");
    c = pauseHover+45;
    rect(pos.x,pos.y,32,32,`rgb(${c},${c},${c})`);
    c = pauseHover + 69;
    cc = `rgb(${c},${c},${c})`;
    if(paused) {
        shape(pos.x,pos.y,[{x:-7,y:-10},{x:-7,y:10},{x:10,y:0}],cc);
    } else {
        rect(pos.x+6,pos.y,6,20,cc);
        rect(pos.x-6,pos.y,6,20,cc);
    }
}

function drawOptionsMenu() {
    if(optionsMenu) {
        let pos = {x:cw/2-100,y:ch/2-100};
        rect(cw/2,ch/2,200,200,"#242424");
        text("Screen Size:",pos.x+2,pos.y+2,"white",2);
            let b = optionsButtons.screenSize;
            rect(b.x,b.y,b.w,b.h,"#444444");
            text(screenSize,pos.x+145,pos.y+4,"white",2);
        text("sfx",pos.x+2,pos.y+30,"white",2);
            b = optionsButtons.sfx;
            rect(b.x,b.y,b.w,b.h-10,"#444444");
            rect((b.x-60)+(volume.sfx*120),b.y,8,20,"#444444");
        text("bgm",pos.x+2,pos.y+60,"white",2);
            b = optionsButtons.bmg;
            rect(b.x,b.y,b.w,b.h-10,"#444444");
            rect((b.x-60)+(volume.bgm*120),b.y,8,20,"#444444");
    }
}
var k={a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,BACKTICK:192,MINUS:189,EQUALS:187,OPENSQUARE:219,ENDSQUARE:221,SEMICOLON:186,SINGLEQUOTE:222,BACKSLASH:220,COMMA:188,PERIOD:190,SLASH:191,ENTER:13,BACKSPACE:8,TAB:9,CAPSLOCK:20,SHIFT:16,CONTROL:17,ALT:18,META:91,LEFTBACKSLASH:226,ESCAPE:27,HOME:36,END:35,PAGEUP:33,PAGEDOWN:34,DELETE:46,INSERT:45,PAUSE:19,UP:38,DOWN:40,LEFT:37,RIGHT:39,CONTEXT:93,SPACE:32,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123};
var keyPress = [];
var keyDown = [];
var mousePress = [];
var mouseDown = [];
var scroll = 0;
var mousePos = {
    x:0,
    y:0
}
var preventedEvents = [false,true,true];

function addListenersTo(elementToListenTo) {
    window.addEventListener("keydown",kdown);
    window.addEventListener("keyup",kup);
    elementToListenTo.addEventListener("mousedown",mdown);
    elementToListenTo.addEventListener("mouseup",mup);
    elementToListenTo.addEventListener("mousemove",mmove);
    elementToListenTo.addEventListener("contextmenu",cmenu);
    elementToListenTo.addEventListener("wheel",scrl);
}
function removeListenersFrom(elementToListenTo) {
    window.removeEventListener("keydown",kdown);
    window.removeEventListener("keyup",kup);
    elementToListenTo.removeEventListener("mousedown",mdown);
    elementToListenTo.removeEventListener("mouseup",mup);
    elementToListenTo.removeEventListener("mousemove",mmove);
    elementToListenTo.removeEventListener("contextmenu",cmenu);
    elementToListenTo.removeEventListener("wheel",scrl);
}
function resetInput() {
    for(var i=0;i<keyPress.length;i++){if(keyPress[i]){keyPress[i]=0}}
    for(var i=0;i<mousePress.length;i++){if(mousePress[i]){mousePress[i]=0}}
    scroll=0;
}
function kdown(e) {
    var h=e.keyCode;
    keyPress[h]=keyPress[h]==[][[]]?1:0;
    keyDown[h]=1;
    if(preventedEvents[0]) {e.preventDefault()}
}
function kup(e) {
    var h=e.keyCode;
    delete keyPress[h];
    delete keyDown[h];
}
function mdown(e) {
    var h=e.button;
    mousePress[h]=mousePress[h]==[][[]]?1:0;
    mouseDown[h]=1;
    if(preventedEvents[1]) {e.preventDefault()}
}
function mup(e) {
    var h=e.button;
    delete mousePress[h];
    delete mouseDown[h];
}
function mmove(e) {
    mousePos.x=e.offsetX/(!canvasScale?autoScale:canvasScale);
    mousePos.y=e.offsetY/(!canvasScale?autoScale:canvasScale);    
}
function cmenu(e) {
    if(preventedEvents[1]) {e.preventDefault()}
}
function scrl(e) {
    scroll+=-1*(e.deltaY/100);
    if(preventedEvents[2]) {e.preventDefault()}
}
function dist(point1,point2) {
    let one = (point2.x - point1.x);
    let two = (point2.y - point1.y);
    return Math.sqrt((one*one)+(two*two));
}

function circlecircle(circle1,circle2) {
    if( dist(circle1,circle2) < (circle1.r + circle2.r)) {
		return true;
	} else {
        return false;
    }
}

function circlepoint(circle,point) {
    if( dist(circle,point) < circle.r) {
		return true;
	} else {
        return false;
    }
}

function rectrect(rect1,rect2) {
    if(rect1.x + rect1.w/2 >= rect2.x - rect2.w/2 &&
       rect1.x - rect1.w/2 <= rect2.x + rect2.w/2 &&
       rect1.y + rect1.h/2 >= rect2.y - rect2.h/2 &&
       rect1.y - rect1.h/2 <= rect2.y + rect2.h/2) {
        return true;
    } else {
        return false;
    }
}

function rectpoint(rect,point) {
    if(rect.x + rect.w/2 >= point.x &&
       rect.x - rect.w/2 <= point.x &&
       rect.y + rect.h/2 >= point.y &&
       rect.y - rect.h/2 <= point.y ) {
        return true;
    } else {
        return false;
    }
}

function circlerect(circle,rect) { //credit: https://yal.cc/rectangle-circle-intersection-test/
    let rectHalfWidth  = rect.w/2;
    let rectHalfHeight = rect.h/2;
    let deltaX = circle.x - Math.max(rect.x - rectHalfWidth, Math.min(circle.x, rect.x + rectHalfWidth));
    let deltaY = circle.y - Math.max(rect.y - rectHalfHeight, Math.min(circle.y, rect.y + rectHalfHeight));
    return (deltaX * deltaX + deltaY * deltaY) < (circle.r * circle.r);
}

function circleOnSideRect(circle,rect) {
    let rectHalfWidth  = rect.w/2;
    let rectHalfHeight = rect.h/2;
    let left   = rect.x - rectHalfWidth;
    let right  = rect.x + rectHalfWidth;
    let top    = rect.y - rectHalfHeight;
    let bottom = rect.y + rectHalfHeight;
    let cx = circle.x;
    let cy = circle.y;
    if(cy < top && cx > left && cx < right) { // top side
        return 0;
    } else if(cy > bottom && cx > left && cx < right) { // bottom side
        return 2;
    } else if (cx < left && cy > top && cy < bottom) { // left side
        return 3;
    } else if (cx > right && cy > top && cy < bottom) { // right side
        return 1;
    } else {
        let returnValue=0; // 0 = top, 1 = right, 2 = bottom, 3 = left
        let topleft = dist (circle,{x:left,y:top});
        let topright = dist (circle,{x:right,y:top});
        let bottomleft = dist (circle,{x:left,y:bottom});
        let bottomright = dist (circle,{x:right,y:bottom});
        switch(Math.min(topleft,topright,bottomleft,bottomright)) { // find what corner the cricle is closer to, then determine what side it is closer to
            case topleft:
                var m = slope(rect,{x:left,y:top});
                var mperp = -(1/m);
                var b = yIntercept(rect,m);
                var bperp = yIntercept(circle,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 3;} else {returnValue = 0;}
                break;
            case topright:
                var m = slope(rect,{x:right,y:top});
                var mperp = -(1/m);
                var b = yIntercept(rect,m);
                var bperp = yIntercept(circle,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 0;} else {returnValue = 1;}
                break;
            case bottomleft:
                var m = slope(rect,{x:left,y:bottom});
                var mperp = -(1/m);
                var b = yIntercept(rect,m);
                var bperp = yIntercept(circle,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 3;} else {returnValue = 2;}
                break;
            case bottomright:
                var m = slope(rect,{x:right,y:bottom});
                var mperp = -(1/m);
                var b = yIntercept(rect,m);
                var bperp = yIntercept(circle,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 2;} else {returnValue = 1;}
                break;
        }
        return returnValue;
    }
}

function rectOnSideRect(rect1,rect2) {
    let rectHalfWidth2  = rect2.w/2;
    let rectHalfHeight2 = rect2.h/2;
    let left2   = rect2.x - rectHalfWidth2;
    let right2 = rect2.x + rectHalfWidth2;
    let top2   = rect2.y - rectHalfHeight2;
    let bottom2 = rect2.y + rectHalfHeight2;

    let rectHalfWidth1  = rect1.w/2;
    let rectHalfHeight1 = rect1.h/2;
    let rx1 = rect1.x;
    let ry1 = rect1.y;
    let left1   = rx1 - rectHalfWidth1;
    let right1 = rx1 + rectHalfWidth1;
    let top1   = ry1 - rectHalfHeight1;
    let bottom1 = ry1 + rectHalfHeight1;
    // find what point is closer to the rectangle 
    let topleft1 = dist (rect2,{x:left1,y:top1});
    let topright1 = dist (rect2,{x:right1,y:top1});
    let bottomleft1 = dist (rect2,{x:left1,y:bottom1});
    let bottomright1 = dist (rect2,{x:right1,y:bottom1});
    let topmiddle1 = dist (rect2,{x:rx1,y:top1});
    let rightmiddle1 = dist (rect2,{x:right1,y:ry1});
    let bottommiddle1 = dist (rect2,{x:rx1,y:bottom1});
    let leftmiddle1 = dist (rect2,{x:left1,y:ry1});
    let cx = rx1;
    let cy = ry1;
    switch(Math.min(topleft1,topright1,bottomleft1,bottomright1,topmiddle1,rightmiddle1,bottommiddle1,leftmiddle1)) {
        //set the point we are testing to the closest point to the rectangle
        case topleft1:
            cx -= rect1.w/2;
            cy -= rect1.h/2;
            break;
        case topright1:
            cx += rect1.w/2;
            cy -= rect1.h/2;
            break;
        case bottomleft1:
            cx -= rect1.w/2;
            cy += rect1.h/2;
            break;
        case bottomright1:
            cx += rect1.w/2;
            cy += rect1.h/2;
            break;
        case topmiddle1:
            cy -= rect1.h/2;
            break;
        case rightmiddle1:
            cx += rect1.w/2;
            break;
        case bottommiddle1:
            cy += rect1.h/2;
            break;
        case leftmiddle1:
            cx -= rect1.w/2;
            break;
    }
    if(cy < top2 && cx > left2 && cx < right2) { // top side
        return 0;
    } else if(cy > bottom2 && cx > left2 && cx < right2) { // bottom side
        return 2;
    } else if (cx < left2 && cy > top2 && cy < bottom2) { // left side
        return 3;
    } else if (cx > right2 && cy > top2 && cy < bottom2) { // right side
        return 1;
    } else {
        let returnValue=0; // 0 = top, 1 = right, 2 = bottom, 3 = left
        let determiningPoint = {x:cx,y:cy};
        let topleft = dist (determiningPoint,{x:left2,y:top2});
        let topright = dist (determiningPoint,{x:right2,y:top2});
        let bottomleft = dist (determiningPoint,{x:left2,y:bottom2});
        let bottomright = dist (determiningPoint,{x:right2,y:bottom2});
        switch(Math.min(topleft,topright,bottomleft,bottomright)) { // find what corner the point is closer to, then determine what side it is closer to
            case topleft:
                var m = slope(rect2,{x:left2,y:top2});
                var mperp = -(1/m);
                var b = yIntercept(rect2,m);
                var bperp = yIntercept(determiningPoint,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 3;} else {returnValue = 0;}
                break;
            case topright:
                var m = slope(rect2,{x:right2,y:top2});
                var mperp = -(1/m);
                var b = yIntercept(rect2,m);
                var bperp = yIntercept(determiningPoint,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 0;} else {returnValue = 1;}
                break;
            case bottomleft:
                var m = slope(rect2,{x:left2,y:bottom2});
                var mperp = -(1/m);
                var b = yIntercept(rect2,m);
                var bperp = yIntercept(determiningPoint,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 3;} else {returnValue = 2;}
                break;
            case bottomright:
                var m = slope(rect2,{x:right2,y:bottom2});
                var mperp = -(1/m);
                var b = yIntercept(rect2,m);
                var bperp = yIntercept(determiningPoint,mperp);
                var mid = POI(m,b,mperp,bperp);
                if(cx<mid) {returnValue = 2;} else {returnValue = 1;}
                break;
        }
        return returnValue;
    }
}

function slope(point1,point2) {
    return ((point2.y-point1.y)/(point2.x-point1.x));
}

function yIntercept(point,slope) {
    return point.y - (slope * point.x);
}

function POI(m1,b1,m2,b2) {
    x = (b2 - b1) / (m1 - m2);
    return x;
    //y = m1 * x + b1;
}

function ifRectOnEdgeBounce(rect) {
    let rx = rect.x;
    let ry = rect.y;
    let rw = rect.w/2;
    let rh = rect.h/2;
    if(rx+rw>edge.right) {
        rect.v.x *= -1;
        rect.x = edge.right-rw;
    }
    if(rx-rw<edge.left) {
        rect.v.x *= -1;
        rect.x = edge.left+rw;
    }
    if(ry+rh>edge.bottom) {
        rect.v.y *= -1;
        rect.y = edge.bottom-rh;
    }
    if(ry-rh<edge.top) {
        rect.v.y *= -1;
        rect.y = edge.top+rh;
    }
}

function ifCircleOnEdgeBounce(circle) {
    let cx = circle.x;
    let cy = circle.y;
    let cr = circle.r;
    if(cx+cr>edge.right) {
        circle.v.x *= -1;
        circle.x = edge.right-cr;
    }
    if(cx-cr<edge.left) {
        circle.v.x *= -1;
        circle.x = edge.left+cr;
    }
    if(cy+cr>edge.bottom) {
        circle.v.y *= -1;
        circle.y = edge.bottom-cr;
    }
    if(cy-cr<edge.top) {
        circle.v.y *= -1;
        circle.y = edge.top+cr;
    }
}
// create globals
var canvases={cvs:null,ctx:null,buffer1cvs:null,buffer1ctx:null,buffer2cvs:null,buffer2ctx:null}, // visable and hidden canvases
cw, // canvas width
ch, // canvas height
camera={zoom:1,angle:0,x:0,y:0}, // affects how everything is drawn
updateFPS=60,
gameStarted=false,
drawMode=0, // 0=normal, 1=zoomed, 2=zoomed/rotated, set automatically depending on camera
absDraw=false,
curCtx, // what canvas to draw to
maxCvsSize, // used by second buffer
canvasScale=1,
difx=0, // offsets for drawing
dify=0,
seperateInputLoop=true,
edge={top:null,bottom:null,left:null,right:null}, // used by if___OnEdgeBounce, set to canvas size at setup, can be changed whenever
drawLimitLeft,
drawLimitRight,
drawLimitTop,
drawLimitBottom,
sizeDif,
bug="\uD83D\uDC1B",
loadingCircle,
loadAng=0,
optionsHover=0,
pauseHover=0,
optionsMenu=false,
optionsButtons={},
clickSound,
paused=false,
screenSize="1:1",
autoScale=1,

images=[], // put image paths here
imagePaths=[],
imgs=[],
sprites={}, // loaded images

audio=[], // put audio paths here
audioPaths=[],
sounds={}, // loaded sounds
abuffer = [], // audio nodes shoved here
volumeList = [], // gain nodes shoved here
audioLoadedLength=0,
volume={sfx:1,bgm:1};
/* options
    future:
        modefileable checklist
        key bindings
*/

cursor = {sprite:null,alignment:1,show:true}, // 0=topleft, 1=centered
mouseOnCanvas=false;

const acceptableChars="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_-. ";//for image names

const AudioContext = window.AudioContext||window.webkitAudioContext;
var context;
var sfxVolumeNode;
var bmgVolumeNode;

document.getElementById("game").onmouseout = function()   {mouseOnCanvas = false;}
document.getElementById("game").onmouseover = function()   {mouseOnCanvas = true;}

//setup canvases and input
function setup(physicsFPS) {
    updateFPS = physicsFPS;
    
    canvases.cvs = document.getElementById("game");
    canvases.ctx = canvases.cvs.getContext("2d", { alpha: false });

    canvases.cvs.onmousedown = function () {if(!gameStarted){loadImagesAndSounds();gameStarted=true;}}

    createCanvas("buffer1");
    createCanvas("buffer2");

    canvases.ctx.imageSmoothingEnabled = false;
    canvases.buffer1ctx.imageSmoothingEnabled = false;
    canvases.buffer2ctx.imageSmoothingEnabled = false;

    maxCvsSize=Math.max(canvases.cvs.width,canvases.cvs.height);
    sizeDif=maxCvsSize-Math.min(canvases.cvs.width,canvases.cvs.height);
    cw=canvases.cvs.width;
    ch=canvases.cvs.height;
    
    edge={top:0,bottom:ch,left:0,right:cw};

    addFont();
    addStyle();

    addListenersTo(canvases.cvs);

    curCtx = canvases.ctx;
    requestAnimationFrame(startButton);
    function startButton() {
        curCtx.fillStyle="#2d2d2d";
        curCtx.fillRect(0,0,cw,ch);//debugger;
        circle(cw/2,ch/2,27,"#066312");
        circle(cw/2,ch/2,23,"#149124");
        shape(cw/2,ch/2,[{x:-7,y:-15},{x:-7,y:15},{x:15,y:0}],"#47f55d");
        if(!gameStarted) {requestAnimationFrame(startButton);}
    }
}

function drawLoop() {
    cw=canvases.cvs.width;
    ch=canvases.cvs.height;
    scaleCanvases();

    switchDrawMode();
    
    resizeBuffers();

    clearCanvases();

    var limitModifyer = 0;
    if(drawMode==2) {limitModifyer=canvases.buffer2cvs.width-maxCvsSize;}
    drawLimitLeft   = -camera.x - (drawMode==2?sizeDif:0) - limitModifyer;
    drawLimitRight  = -camera.x + maxCvsSize + (drawMode==2?sizeDif:0) + limitModifyer;
    drawLimitTop    = -camera.y -(drawMode==2?sizeDif:0) - limitModifyer;
    drawLimitBottom = -camera.y + maxCvsSize + (drawMode==2?sizeDif:0) + limitModifyer;

    draw();
    
    render();

    curCtx=canvases.ctx;
    difx=0;dify=0;
    var camCache = {x:camera.x,y:camera.y};
    var drawModeCache = drawMode;
    camera.x=0;camera.y=0;
    drawMode=0;
    absDraw=true;
    absoluteDraw();
    absDraw=false;

    drawButtons();
    drawOptionsMenu();
    drawCursor();

    drawMode=drawModeCache;

    camera.x = camCache.x;
    camera.y = camCache.y;

    requestAnimationFrame(drawLoop);
}

function updateLoop() {
    if(seperateInputLoop==false) {
        handleOptionsInput();
    }
    sfxVolumeNode.gain.value = volume.sfx;
    bmgVolumeNode.gain.value = volume.bgm;
    if(!paused) {
        update();
    }

    if(seperateInputLoop==false) {
        resetInput();
    }
}


function inputLoop() {
    handleOptionsInput();
    if(!paused) {
        input();
    }

    resetInput();
}