#include "player_remote.h"
 #include "ent_skateshop.h"
 #include "shaders/model_entity.h"
+#include "shaders/model_sky_cubemap.h"
 
 struct world_render world_render;
 
-static int ccmd_set_time( int argc, const char *argv[] ){
+static int ccmd_render_portals( int argc, const char *argv[] );
+
+static int ccmd_set_time( int argc, const char *argv[] )
+{
    world_instance *world = &_world.main;
    if( argc == 1 )
       world->time = atof( argv[0] );
    VG_VAR_I32( k_light_preview );
    VG_VAR_I32( k_light_editor );
    vg_console_reg_cmd( "set_time", ccmd_set_time, NULL );
+   vg_console_reg_cmd( "render_portals", ccmd_render_portals, NULL );
 
    world_render.sky_rate = 1.0;
    world_render.sky_target_rate = 1.0;
    mdl_open( &msky, "models/rs_skydome.mdl", vg_mem.scratch );
    mdl_load_metadata_block( &msky, vg_mem.scratch );
    mdl_async_load_glmesh( &msky, &world_render.skydome, NULL );
+   world_render.skydome_complete_mesh = *mdl_find_submesh( &msky, "dome_complete" );
+   world_render.skydome_squanched_mesh = *mdl_find_submesh( &msky, "dome_squanched" );
    mdl_close( &msky );
 
    vg_info( "Loading default world textures\n" );
       glActiveTexture( GL_TEXTURE0 );
       glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
    }
+   else if( world->skybox == k_skybox_cubemap )
+   {
+      /* TODO */
+   }
    else {
       vg_fatal_error( "Programming error\n" );
    }
    glDisable( GL_DEPTH_TEST );
 
    mesh_bind( &world_render.skydome );
-   mesh_draw( &world_render.skydome );
+   mdl_draw_submesh( &world_render.skydome_complete_mesh );
    
    glEnable( GL_DEPTH_TEST );
    glDepthMask( GL_TRUE );
 
          if( gate->flags & k_ent_gate_linked )
          {
+            render_gate( world, NULL, gate, cam );
+
+            m4x3f mmdl;
+            m4x4f pvm;
+            
+            m4x3_copy( gate->to_world, mmdl );
+            mmdl[3][1] += 2.0f;
+
+            if( gate->flags & k_ent_gate_flip )
+            {
+               m3x3f rotation = {{-1,0,0},{0,1,0},{0,0,-1}};
+               m3x3_mul( mmdl, rotation, mmdl );
+            }
+
+            m4x3_expand( mmdl, pvm );
+            m4x4_mul( cam->mtx_prev.pv, pvm, pvm );
+
+            shader_model_sky_cubemap_use();
+            shader_model_sky_cubemap_uMdl( mmdl );
+            shader_model_sky_cubemap_uPv( cam->mtx.pv );
+            shader_model_sky_cubemap_uPvmPrev( pvm );
+            shader_model_sky_cubemap_uTexCubemap(10);
+
+            glActiveTexture( GL_TEXTURE10 );
+            glBindTexture( GL_TEXTURE_CUBE_MAP, world->nonlocal_gates_cubemaps[ gate->cubemap_id ] );
 
+            glClear( GL_DEPTH_BUFFER_BIT );
+            glStencilFunc( GL_EQUAL, 1, 0xFF );
+            glStencilMask( 0x00 ); 
+            glEnable( GL_CULL_FACE );
+            glEnable( GL_STENCIL_TEST );
+
+            mesh_bind( &world_render.skydome );
+            mdl_draw_submesh( &world_render.skydome_squanched_mesh );
+
+            glStencilMask( 0xFF );
+            glStencilFunc( GL_ALWAYS, 1, 0xFF );
+            glDisable( GL_STENCIL_TEST );
          }
          else
          {
    render_world_fxglow( world, world, cam, mmdl, 0, 0, 1 );
 }
 
-static void render_cubemap_side( world_instance *world, ent_cubemap *cm, 
-                                    u32 side ){
+static void render_cubemap_side( world_instance *world, v3f co, m3x3f rotation, u32 side )
+{
    vg_camera cam;
-   glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-         GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, cm->texture_id, 0 );
-   glClear( GL_DEPTH_BUFFER_BIT );
-
    v3f forward[6] = {
       { -1.0f,  0.0f,  0.0f },
       {  1.0f,  0.0f,  0.0f },
       { 0.0f, -1.0f,  0.0f }
    };
 
+   v3f vz, vy;
+   m3x3_mulv( rotation, forward[side], vz );
+   m3x3_mulv( rotation, up[side], vy );
+
    v3_zero( cam.angles );
-   v3_copy( cm->co, cam.pos );
+   v3_copy( co, cam.pos );
 
-   v3_copy( forward[side], cam.transform[2] );
-   v3_copy( up[side], cam.transform[1] );
-   v3_cross( up[side], forward[side], cam.transform[0] );
-   v3_copy( cm->co, cam.transform[3] );
+   v3_copy( vz, cam.transform[2] );
+   v3_copy( vy, cam.transform[1] );
+   v3_cross( vy, vz, cam.transform[0] );
+   v3_copy( co, cam.transform[3] );
    m4x3_invert_affine( cam.transform, cam.transform_inverse );
 
    vg_camera_update_view( &cam );
    vg_camera_finalize( &cam );
    vg_camera_finalize( &cam );
 
-   render_world( world, &cam, 0, 1, 1, 0 );
+   /* TODO: CANT RENDER CUBEMAPS BECAUSE RENDER_WORLD FUCKS WITH GLVIEWPORT AND STUFF */
+   render_world( world, &cam, 0, 1, 0, 0 );
 }
 
 void render_world_cubemaps( world_instance *world )
 {
+   m3x3f identity;
+   m3x3_identity( identity );
+
    if( world->cubemap_cooldown )
       world->cubemap_cooldown --;
    else{
       world->cubemap_cooldown = 60;
 
       glViewport( 0, 0, WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES );
-      for( u32 i=0; i<af_arrcount( &world->ent_cubemap ); i++ ){
+      for( u32 i=0; i<af_arrcount( &world->ent_cubemap ); i++ )
+      {
          ent_cubemap *cm = af_arritm( &world->ent_cubemap, i );
          glBindFramebuffer( GL_FRAMEBUFFER, cm->framebuffer_id );
 
          if( world->cubemap_side >= 6 )
             world->cubemap_side = 0;
 
-         render_cubemap_side( world, cm, world->cubemap_side );
+         glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+               GL_TEXTURE_CUBE_MAP_POSITIVE_X + world->cubemap_side, 
+               cm->texture_id, 0 );
+         glClear( GL_DEPTH_BUFFER_BIT );
+
+         render_cubemap_side( world, cm->co, identity, world->cubemap_side );
       }
    }
 }
 
+void render_world_portal_cubemaps(void)
+{
+   if( vg_loader_availible() )
+   {
+      qoi_desc desc = 
+      {
+         .width = 512,
+         .height = 512*6,
+         .channels = 3,
+         .colorspace = 0
+      };
+
+      vg_linear_clear( vg_async.buffer );
+      u8 *src_image = vg_linear_alloc( vg_async.buffer, 512*512*3*6 );
+      u8 *compressed_image = vg_linear_alloc( vg_async.buffer, vg_query_qoi_storage_size( &desc ) );
+   
+      GLuint temp_tex, temp_fb, temp_rb;
+
+      glGenFramebuffers( 1, &temp_fb );
+      glBindFramebuffer( GL_FRAMEBUFFER, temp_fb );
+
+      glGenRenderbuffers( 1, &temp_rb );
+      glBindRenderbuffer( GL_RENDERBUFFER, temp_rb );
+      glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512 );
+      glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, temp_rb );
+
+      glGenTextures( 1, &temp_tex );
+      glBindTexture( GL_TEXTURE_2D, temp_tex );
+      glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
+      glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, temp_tex, 0 );
+
+               if( glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE )
+      {
+         vg_error( "Cubemap framebuffer incomplete.\n" );
+         return;
+      }
+
+      glViewport( 0, 0, 512, 512 );
+
+      world_instance *world = &_world.main;
+
+      for( u32 j=0; j<af_arrcount(&world->ent_gate); j ++ )
+      {
+         ent_gate *gate = af_arritm( &world->ent_gate, j );
+         if( gate->flags & k_ent_gate_nonlocal )
+         {
+            v3f co;
+            v3_add( gate->co[0], (v3f){0,2,0}, co );
+
+            m3x3f rotation;
+            m3x3f correction = {{0,0,1},{0,1,0},{-1,0,0}};
+            m3x3_mul( correction, gate->to_world, rotation );
+
+            for( u32 i=0; i<6; i ++ )
+            {
+               glClear( GL_DEPTH_BUFFER_BIT );
+               render_cubemap_side( &_world.main, co, gate->to_world, i );
+
+               u8 *dest_side = src_image + i*512*512*3;
+               glReadBuffer( GL_COLOR_ATTACHMENT0 );
+               glReadPixels( 0,0, 512,512, GL_RGB, GL_UNSIGNED_BYTE, dest_side );
+            }
+
+            char path[256];
+            nonlocal_gate_cubemap_path( world->addon, af_str( &world->meta.af, gate->key ), path );
+
+            int file_size;
+            if( vg_encode_qoi2( src_image, &desc, compressed_image, &file_size ) )
+            {
+               vg_asset_write( path, compressed_image, file_size );
+            }
+         }
+      }
+
+      glDeleteTextures( 1, &temp_tex );
+      glDeleteFramebuffers( 1, &temp_fb );
+      glDeleteRenderbuffers( 1, &temp_rb );
+   }
+}
+
+static int ccmd_render_portals( int argc, const char *argv[] )
+{
+   render_world_portal_cubemaps();
+   return 1;
+}
+
 /*
  * Geo shaders
  * ---------------------------------------------