package alternativa.engine3d.core
{
   import alternativa.engine3d.alternativa3d;
   import alternativa.engine3d.materials.ShaderProgram;
   import flash.display3D.Context3D;
   import flash.display3D.Context3DCompareMode;
   import flash.display3D.Context3DProgramType;
   import flash.display3D.IndexBuffer3D;
   import flash.display3D.Program3D;
   
   use namespace alternativa3d;
   
   public class Renderer
   {
      protected static var collector:DrawUnit;
      
      private static var _usedBuffers:uint = 0;
      
      private static var _usedTextures:uint = 0;
      
      alternativa3d var camera:Camera3D;
      
      alternativa3d var name_T5:Vector.<DrawUnit> = new Vector.<DrawUnit>();
      
      public function Renderer()
      {
         super();
      }
      
      alternativa3d function render(context:Context3D) : void
      {
         var list:DrawUnit = null;
         var next:DrawUnit = null;
         var drawUnitsLength:int = int(this.name_T5.length);
         for(var i:int = 0; i < drawUnitsLength; i++)
         {
            list = this.name_T5[i];
            if(list != null)
            {
               switch(i)
               {
                  case RenderPriority.SKY:
                     context.setDepthTest(false,Context3DCompareMode.ALWAYS);
                     break;
                  case RenderPriority.OPAQUE_SORT:
                     context.setDepthTest(true,Context3DCompareMode.LESS);
                     break;
                  case RenderPriority.OPAQUE:
                     context.setDepthTest(true,Context3DCompareMode.LESS);
                     break;
                  case RenderPriority.TANK_SHADOW:
                     context.setDepthTest(false,Context3DCompareMode.LESS_EQUAL);
                     break;
                  case RenderPriority.TANK_OPAQUE:
                     context.setDepthTest(true,Context3DCompareMode.LESS);
                     break;
                  case RenderPriority.DECALS:
                     context.setDepthTest(false,Context3DCompareMode.LESS_EQUAL);
                     break;
                  case RenderPriority.SHADOWS:
                     context.setDepthTest(false,Context3DCompareMode.EQUAL);
                     break;
                  case RenderPriority.SHADOWED_LIGHTS:
                     context.setDepthTest(false,Context3DCompareMode.EQUAL);
                     break;
                  case RenderPriority.LIGHTS:
                     context.setDepthTest(false,Context3DCompareMode.EQUAL);
                     break;
                  case RenderPriority.FOG:
                     context.setDepthTest(false,Context3DCompareMode.EQUAL);
                     break;
                  case RenderPriority.TRANSPARENT_SORT:
                     if(list.alternativa3d::next != null)
                     {
                        list = this.alternativa3d::sortByAverageZ(list);
                     }
                     context.setDepthTest(false,Context3DCompareMode.LESS);
                     break;
                  case RenderPriority.NEXT_LAYER:
                     context.setDepthTest(false,Context3DCompareMode.ALWAYS);
                     break;
                  case RenderPriority.EFFECTS:
                     if(list.alternativa3d::next != null)
                     {
                        list = this.alternativa3d::sortByAverageZ(list);
                     }
                     context.setDepthTest(false,Context3DCompareMode.LESS);
               }
               // Rendering, XXX: this was missing from the decompile so I just coppied the code from latest A3D (8.32)
               while (list != null) {
                  next = list.next;
                  renderDrawUnit(list, context, camera);
                  // Send to collector
                  list.clear();
                  list.next = collector;
                  collector = list;
                  list = next;
               }
            }
         }

         this.name_T5.length = 0;
      }
      
      alternativa3d function createDrawUnit(object:Object3D, program:Program3D, indexBuffer:IndexBuffer3D, firstIndex:int, numTriangles:int, debugShader:ShaderProgram = null) : DrawUnit
      {
         var res:DrawUnit = null;
         if(collector != null)
         {
            res = collector;
            collector = collector.alternativa3d::next;
            res.alternativa3d::next = null;
         }
         else
         {
            res = new DrawUnit();
         }
         res.alternativa3d::object = object;
         res.alternativa3d::program = program;
         res.alternativa3d::indexBuffer = indexBuffer;
         res.alternativa3d::firstIndex = firstIndex;
         res.alternativa3d::numTriangles = numTriangles;
         return res;
      }
      
      alternativa3d function addDrawUnit(drawUnit:DrawUnit, renderPriority:int) : void
      {
         if(renderPriority >= this.name_T5.length)
         {
            this.name_T5.length = renderPriority + 1;
         }
         drawUnit.alternativa3d::next = this.name_T5[renderPriority];
         this.name_T5[renderPriority] = drawUnit;
      }
      
      protected function renderDrawUnit(drawUnit:DrawUnit, context:Context3D, camera:Camera3D) : void
      {
         var bufferIndex:int = 0;
         var bufferBit:int = 0;
         var currentBuffers:int = 0;
         var textureSampler:int = 0;
         var textureBit:int = 0;
         var currentTextures:int = 0;
         context.setBlendFactors(drawUnit.alternativa3d::blendSource,drawUnit.alternativa3d::blendDestination);
         context.setCulling(drawUnit.alternativa3d::culling);
         for(var i:int = 0; i < drawUnit.name_3G; i++)
         {
            bufferIndex = int(drawUnit.name_else[i]);
            bufferBit = 1 << bufferIndex;
            currentBuffers |= bufferBit;
            _usedBuffers &= ~bufferBit;
            context.setVertexBufferAt(bufferIndex,drawUnit.alternativa3d::vertexBuffers[i],drawUnit.name_nw[i],drawUnit.name_EL[i]);
         }
         if(drawUnit.name_9X > 0)
         {
            context.setProgramConstantsFromVector(Context3DProgramType.VERTEX,0,drawUnit.name_Aq,drawUnit.name_9X);
         }
         if(drawUnit.name_Kv > 0)
         {
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,0,drawUnit.name_Cl,drawUnit.name_Kv);
         }
         for(i = 0; i < drawUnit.name_Oq; )
         {
            textureSampler = int(drawUnit.name_kR[i]);
            textureBit = 1 << textureSampler;
            currentTextures |= textureBit;
            _usedTextures &= ~textureBit;
            context.setTextureAt(textureSampler,drawUnit.alternativa3d::textures[i]);
            i++;
         }
         context.setProgram(drawUnit.alternativa3d::program);
         for(bufferIndex = 0; _usedBuffers > 0; )
         {
            bufferBit = _usedBuffers & 1;
            _usedBuffers >>= 1;
            if(Boolean(bufferBit))
            {
               context.setVertexBufferAt(bufferIndex,null);
            }
            bufferIndex++;
         }
         for(textureSampler = 0; _usedTextures > 0; )
         {
            textureBit = _usedTextures & 1;
            _usedTextures >>= 1;
            if(Boolean(textureBit))
            {
               context.setTextureAt(textureSampler,null);
            }
            textureSampler++;
         }
         context.drawTriangles(drawUnit.alternativa3d::indexBuffer,drawUnit.alternativa3d::firstIndex,drawUnit.alternativa3d::numTriangles);
         _usedBuffers = currentBuffers;
         _usedTextures = currentTextures;
         ++camera.alternativa3d::numDraws;
         camera.alternativa3d::numTriangles += drawUnit.alternativa3d::numTriangles;
      }
      
      alternativa3d function sortByAverageZ(list:DrawUnit, direction:Boolean = true) : DrawUnit
      {
         var left:DrawUnit = list;
         var right:DrawUnit = list.alternativa3d::next;
         while(right != null && right.alternativa3d::next != null)
         {
            list = list.alternativa3d::next;
            right = right.alternativa3d::next.alternativa3d::next;
         }
         right = list.alternativa3d::next;
         list.alternativa3d::next = null;
         if(left.alternativa3d::next != null)
         {
            left = this.alternativa3d::sortByAverageZ(left,direction);
         }
         if(right.alternativa3d::next != null)
         {
            right = this.alternativa3d::sortByAverageZ(right,direction);
         }
         var flag:Boolean = direction ? left.alternativa3d::object.alternativa3d::localToCameraTransform.l > right.alternativa3d::object.alternativa3d::localToCameraTransform.l : left.alternativa3d::object.alternativa3d::localToCameraTransform.l < right.alternativa3d::object.alternativa3d::localToCameraTransform.l;
         if(flag)
         {
            list = left;
            left = left.alternativa3d::next;
         }
         else
         {
            list = right;
            right = right.alternativa3d::next;
         }
         var last:DrawUnit = list;
         while(left != null)
         {
            if(right == null)
            {
               last.alternativa3d::next = left;
               return list;
            }
            if(flag)
            {
               if(direction ? left.alternativa3d::object.alternativa3d::localToCameraTransform.l > right.alternativa3d::object.alternativa3d::localToCameraTransform.l : left.alternativa3d::object.alternativa3d::localToCameraTransform.l < right.alternativa3d::object.alternativa3d::localToCameraTransform.l)
               {
                  last = left;
                  left = left.alternativa3d::next;
               }
               else
               {
                  last.alternativa3d::next = right;
                  last = right;
                  right = right.alternativa3d::next;
                  flag = false;
               }
            }
            else if(direction ? left.alternativa3d::object.alternativa3d::localToCameraTransform.l < right.alternativa3d::object.alternativa3d::localToCameraTransform.l : left.alternativa3d::object.alternativa3d::localToCameraTransform.l > right.alternativa3d::object.alternativa3d::localToCameraTransform.l)
            {
               last = right;
               right = right.alternativa3d::next;
            }
            else
            {
               last.alternativa3d::next = left;
               last = left;
               left = left.alternativa3d::next;
               flag = true;
            }
         }
         last.alternativa3d::next = right;
         return list;
      }
   }
}

