/*
 *  Copyright 2009 OpenSourceStewardshipFoundation.org
 *  Licensed under GNU General Public License version 2
 *
 * NOTE: this version of SRSW correct as of April 25, 2010
 *
 * Author: seanhalle@yahoo.com
 */


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "PrivateQueue.h"



//===========================================================================

/*This kind of queue is private to a single core at a time -- has no
 * synchronizations 
 */

PrivQueueStruc* makePrivQ()
 {
   PrivQueueStruc* retQ;
      //This malloc is not safe to use in wrapper lib nor app code!
   retQ = (PrivQueueStruc *) VMS_int__malloc( sizeof( PrivQueueStruc ) );

      //This malloc is not safe to use in wrapper lib nor app code!
   retQ->startOfData = VMS_WL__malloc( 1024 * sizeof(void *) );
   memset( retQ->startOfData, 0, 1024 * sizeof(void *) );
   retQ->extractPos = &(retQ->startOfData[0]); //side by side == empty
   retQ->insertPos  = &(retQ->startOfData[1]); // so start pos's have to be
   retQ->endOfData  = &(retQ->startOfData[1023]);

   return retQ;
 }


void
enlargePrivQ( PrivQueueStruc *Q )
 { int32  oldSize, newSize, topPartSize, bottPartSize;
   int8  *insertPos, *extractPos;
   int8  *oldStartOfData, *oldEndOfData, *newStartOfData, *newEndOfData;
   int32  insertOffsetBytes, extractOffsetBytes;
   int8  *copyStartAddr;

   oldStartOfData = (int8 *)Q->startOfData;
   oldEndOfData   = (int8 *)Q->endOfData;
   insertPos      = (int8 *)Q->insertPos;
   extractPos     = (int8 *)Q->extractPos;
   
      //TODO: verify these get number of bytes correct
   insertOffsetBytes  = (int32)(insertPos  - oldStartOfData);
   extractOffsetBytes = (int32)(extractPos - oldStartOfData);
   
   oldSize            = oldEndOfData - oldStartOfData + 1; //in bytes
   newSize            = 2 * oldSize;
   
      //This malloc is not safe to use in wrapper lib nor app code!
   Q->startOfData     = (void **)VMS_int__malloc( newSize );
   newStartOfData     = (int8 *)Q->startOfData;
   newEndOfData       = newStartOfData + newSize; //all calcs in Bytes
   Q->endOfData       = (void **)newEndOfData;
   
      //TODO: test all of this, for both cases

      //Moving the data and pointers to the new array is
      //a little trickier than at first it seems..  the top part
      // of old queue must be moved to the top part of new queue, while
      // bottom part of old to bottom part of new, then the new insert
      // and extract positions calculated by offset from top and bottom
      //UNLESS the one case where old extract was at bottom and insert
      // was at top.
      //TODO: check that this is correct!
   if( extractPos == oldStartOfData && insertPos == oldEndOfData )
    {
      memcpy( newStartOfData, oldStartOfData, oldSize ); //oldSize is bytes
      Q->extractPos  = Q->startOfData; //start of valid data
      Q->insertPos   = Q->startOfData + oldSize - 1; //end of valid data
    }
   else //have to copy two parts separately, then calc positions
    {    //TODO: check end-addr, sizes, and new positions carefully

         //copy top part, starting at extract up until end of data,
         // into top of new array
      topPartSize = oldEndOfData - extractPos + 1; //+1 includes extractPos
      copyStartAddr = newEndOfData - topPartSize + 1;//+1 cancels other
      memcpy( copyStartAddr, Q->extractPos, topPartSize );
      Q->extractPos = (void **)copyStartAddr; //extract just-copied data
  
         //copy bottom part, from old start up to old insert,
         // into bottom of new array		 
      bottPartSize  = oldSize - topPartSize - 1; //-1 for empty insertPos
      memcpy( newStartOfData, oldStartOfData, bottPartSize );
      Q->insertPos  = (void **)(newStartOfData + bottPartSize);
    }
      //This free is not safe to use in wrapper lib nor app code!
   VMS_int__free(oldStartOfData);    
 }


/*Returns NULL when queue is empty
 */
void* readPrivQ( PrivQueueStruc* Q )
 { void *out    = 0;
   void **startOfData = Q->startOfData;
   void **endOfData   = Q->endOfData;

   void **insertPos  = Q->insertPos;
   void **extractPos = Q->extractPos;

      //if not empty -- (extract is just below insert when empty)
   if( insertPos - extractPos != 1 &&
       !(extractPos == endOfData && insertPos == startOfData))
    {    //move before read
      if( extractPos == endOfData ) //write new pos exactly once, correctly
       { Q->extractPos = startOfData; //can't overrun then fix it 'cause
       }                              // other thread might read bad pos
      else
       { Q->extractPos++;
       }
      out = *(Q->extractPos);
      return out;
    }
      //Q is empty
   return NULL;
 }


/*Expands the queue size automatically when it's full
 */
void
writePrivQ( void * in, PrivQueueStruc* Q )
 {
   void **startOfData = Q->startOfData;
   void **endOfData   = Q->endOfData;
   
   void **insertPos  = Q->insertPos;
   void **extractPos = Q->extractPos;

tryAgain:
      //Full? (insert is just below extract when full)
   if( extractPos - insertPos != 1 &&
       !(insertPos == endOfData && extractPos == startOfData))
    { *(Q->insertPos) = in;   //insert before move
      if( insertPos == endOfData ) //write new pos exactly once, correctly
       { Q->insertPos = startOfData;
       }
      else
       { Q->insertPos++;
       }
      return;
    }
      //Q is full
   enlargePrivQ( Q );
   goto tryAgain;
 }


/*Returns false when the queue was full.
 * have option of calling make_larger_PrivQ to make more room, then try again
 */
bool32
writeIfSpacePrivQ( void * in, PrivQueueStruc* Q )
 {
   void **startOfData = Q->startOfData;
   void **endOfData   = Q->endOfData;

   void **insertPos  = Q->insertPos;
   void **extractPos = Q->extractPos;

   if( extractPos - insertPos != 1 &&
       !(insertPos == endOfData && extractPos == startOfData))
    { *(Q->insertPos) = in;   //insert before move
      if( insertPos == endOfData ) //write new pos exactly once, correctly
       { Q->insertPos = startOfData;
       }
      else
       { Q->insertPos++;
       }
      return TRUE;
    }
      //Q is full
   return FALSE;
 }

int32
numInPrivQ( PrivQueueStruc *Q )
 { int32 size, numIn;
 
   if( Q->insertPos < Q->extractPos )
    {    //insert has wrapped around so numIn is:
         // insertPos + size - extractPos -- Consider, is empty when
         // extractPos = endOfData and insert = start -- correctly get zero
      size  = Q->endOfData - Q->startOfData + 1; //sz of 10 is 0..9
      numIn = Q->insertPos - Q->extractPos + size - 1; //-1 bec insrt empty
    }
   else
    {
      numIn =  Q->insertPos -  Q->extractPos -1;//-1 bec insertPos empty
    }
   return numIn;
 }


/*Treats queue as a stack -- no matter contents, if read done right after
 * a push, then the pushed item is what comes out.
 * Expands the queue size automatically when it's full.
 */
void
pushPrivQ( void * in, PrivQueueStruc* Q )
 {
   while(1){
       void **startOfData = Q->startOfData;
       void **endOfData   = Q->endOfData;

       void **insertPos  = Q->insertPos;
       void **extractPos = Q->extractPos;

          //Full? (insert is just below extract when full)
       if( extractPos - insertPos != 1 &&
           !(insertPos == endOfData && extractPos == startOfData))
        {    //insert -- but go backwards, inserting at read position then
             // move read pos backwards
          *(Q->extractPos) = in;
          if( extractPos == startOfData ) //write new pos exactly once, correctly
           { Q->extractPos = endOfData;   //can't overrun then fix it 'cause
           }                              // other thread might read bad pos
          else
           { Q->extractPos--;
           }
          return;
        }
          //Q is full
       enlargePrivQ( Q );
   }
 }


 void
 freePrivQ( PrivQueueStruc *Q )
  {
      //This free is not safe to use in wrapper lib nor app code!
    VMS_int__free( Q->startOfData );
	VMS_int__free( Q );
  }