#include "mgen.h"
#include "mgenMsg.h"
#include "mgenPattern.h"

#include <string.h>
#include <stdio.h>   
#include <time.h>    // for gmtime()
#include <ctype.h>   // for toupper()

MgenPattern::MgenPattern()
: interval_remainder(0.0), burst_pattern(NULL)
{
    
}

MgenPattern::~MgenPattern()
{
    if (burst_pattern) 
    {
        delete burst_pattern;  
        burst_pattern = NULL;
    } 
}

const StringMapper MgenPattern::TYPE_LIST[] = 
{
    {"PERIODIC", PERIODIC},
    {"POISSON", POISSON},
    {"BURST", BURST},
    {"JITTER", JITTER},
    {"XXXX", INVALID_TYPE}   
};


MgenPattern::Type MgenPattern::GetTypeFromString(const char* string)
{
    // Make comparison case-insensitive
    char upperString[16];
    unsigned int len = strlen(string);
    len = len < 15 ? len : 15;
    unsigned int i;
    for (i =0 ; i < len; i++)
        upperString[i] = toupper(string[i]);
    upperString[i] = '\0';
    unsigned int matchCount = 0;
    Type patternType = INVALID_TYPE;
    const StringMapper* m = TYPE_LIST;
    while (INVALID_TYPE != (*m).key)
    {
        if (!strncmp(upperString, (*m).string, len))
        {
            patternType = ((Type)((*m).key));
            matchCount++;
        }
        m++; 
    }
    if (matchCount > 1)
    {
        DMSG(0, "MgenPattern::GetTypeFromString() error: ambiguous pattern type\n");    
        return INVALID_TYPE;
    }
    else
    {
        return patternType;
    }
}  // end MgenPattern::GetTypeFromString()

const StringMapper MgenPattern::BURST_LIST[] = 
{
    {"REGULAR", REGULAR},
    {"RANDOM",  RANDOM},
    {"XXXX",    INVALID_BURST} 
};

MgenPattern::Burst MgenPattern::GetBurstTypeFromString(const char* string)
{
    // Make comparison case-insensitive
    char upperString[16];
    unsigned int len = strlen(string);
    len = len < 15 ? len : 15;
    unsigned int i;
    for (i =0 ; i < len; i++)
        upperString[i] = toupper(string[i]);
    upperString[i] = '\0';
    unsigned int matchCount = 0;
    Burst burstType = INVALID_BURST;
    const StringMapper* m = BURST_LIST;
    while (INVALID_BURST != (*m).key)
    {
        if (!strncmp(upperString, (*m).string, len))
        {
            burstType = ((Burst)((*m).key));
            matchCount++;
        }
        m++;
    }
    if (matchCount > 1)
    {
        DMSG(0, "MgenPattern::GetBurstTypeFromString() Error: ambiguous burst type\n");
        return INVALID_BURST;
    }
    else
    {
        return burstType;
    }
}  // end MgenPattern::GetBurstTypeFromString()

const StringMapper MgenPattern::DURATION_LIST[] = 
{
    {"FIXED",       FIXED},
    {"EXPONENTIAL", EXPONENTIAL},
    {"XXXX",        INVALID_DURATION} 
};

MgenPattern::Duration MgenPattern::GetDurationTypeFromString(const char* string)
{
    // Make comparison case-insensitive
    char upperString[16];
    unsigned int len = strlen(string);
    len = len < 15 ? len : 15;
    unsigned int i;
    for (i =0 ; i < len; i++)
        upperString[i] = toupper(string[i]);
    upperString[i] = '\0';
    unsigned int matchCount = 0;
    Duration durationType = INVALID_DURATION;
    const StringMapper* m = DURATION_LIST;
    while (INVALID_DURATION != (*m).key)
    {
        if (!strncmp(upperString, (*m).string, len))
        {
            durationType = ((Duration)((*m).key));
            matchCount++;
        }
        m++; 
    }
    if (matchCount > 1)
    {
        DMSG(0, "MgenPattern::GetDurationTypeFromString() Error: ambiguous duration type\n");
        return INVALID_DURATION;
    }
    else
    {
        return durationType;
    }
}  // end MgenPattern::GetDurationTypeFromString()


bool MgenPattern::InitFromString(MgenPattern::Type theType, const char* string)
{
    type = theType;
    switch (type)
    {
        case PERIODIC:  // form "<aveRate> <pktSize>"
        case POISSON:
        {
            double aveRate;
            if (2 != sscanf(string, "%lf %hu\n", &aveRate, &pkt_size))
            {
                DMSG(0, "MgenPattern::InitFromString(PERIODIC/POISSON) error: invalid parameters.\n");
                return false; 
            }
            if (aveRate < 0.0)
            {
                if (-1.0 != aveRate)
                {
                    DMSG(0, "MgenPattern::InitFromString(PERIODIC/POISSON) error: invalid packet rate.\n");
                    return false;
                }
                else
                {
                    interval_ave = 0.0;   
                }
            }
            else if (aveRate > 0.0)
            {
                interval_ave = 1.0 / aveRate;
            }
            else
            {
                interval_ave = -1.0;
            }
            if ((pkt_size > MgenMsg::MAX_SIZE) || (pkt_size < MgenMsg::MIN_SIZE))
            {
                DMSG(0, "MgenPattern::InitFromString(PERIODIC/POISSON) error: invalid message size.\n");
                return false;
            }
            break;
        }
        case JITTER:
        {
            double aveRate, jitterFraction;
            interval_remainder = 0.0;
            if (3 != sscanf(string, "%lf %hu %lf\n", &aveRate, &pkt_size, &jitterFraction))
            {
                DMSG(0, "MgenPattern::InitFromString(JITTER) error: invalid parameters.\n");
                return false; 
            }
            if ((jitterFraction < 0.0) || (jitterFraction > 0.5))
            {
                DMSG(0, "MgenPattern::InitFromString(JITTER) error: invalid jitter fraction.\n");
                return false; 
            }
            if (aveRate < 0.0)
            {
                if (-1.0 != aveRate)
                {
                    DMSG(0, "MgenPattern::InitFromString(JITTER) error: invalid packet rate.\n");
                    return false;
                }
                else
                {
                    interval_ave = 0.0;   
                }
            }
            else if (aveRate > 0.0)
            {
                interval_ave = 1.0 / aveRate;
            }
            else
            {
                interval_ave = -1.0;
            }
            if (interval_ave > 0.0)
            {
                jitter_min = interval_ave * (1.0 - jitterFraction);
                jitter_max = interval_ave * (1.0 + jitterFraction);
            }
            else
            {
                jitter_min = jitter_max = interval_ave;
            }
            if ((pkt_size > MgenMsg::MAX_SIZE) || (pkt_size < MgenMsg::MIN_SIZE))
            {
                DMSG(0, "MgenPattern::InitFromString(JITTER) error: invalid message size.\n");
                return false;
            }
            break;
        }
        case BURST: // form "<burstType> <aveInterval> <pattern> [<params>] 
                    //       <durationDistribution> <aveDuration>"
        {
            char fieldBuffer[Mgen::SCRIPT_LINE_MAX+1];
            const char* ptr = string;
            // Strip leading white space
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            if (1 != sscanf(ptr, "%s", fieldBuffer))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: missing burst type.\n");
                return false;
            }
            burst_type = GetBurstTypeFromString(fieldBuffer);
            if (INVALID_BURST == burst_type)
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst type.\n");
                return false;
            } 
            ptr += strlen(fieldBuffer);   
            // Strip leading white space
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            if (1 != sscanf(ptr, "%s", fieldBuffer))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: missing burst interval.\n");
                return false;
            }   
            if (1 != sscanf(fieldBuffer, "%lf", &interval_ave))
            {
                 DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst interval.\n");
                 return false;
            }   
            if (interval_ave < 0.0)
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst interval.\n");
                return false;
            }
            ptr += strlen(fieldBuffer);   
           // Strip leading white space
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            if (1 != sscanf(ptr, "%s", fieldBuffer))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: missing burst pattern.\n");
                return false;
            }   
            Type patternType = GetTypeFromString(fieldBuffer); 
            ptr += strlen(fieldBuffer);
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            // Point to beginning of pattern parameters
            const char* pptr = ptr;
            // Find end of pattern parameter set
            unsigned int nested = 1;
            while (nested)
            {
                ptr++;
                if ('[' == *ptr) 
                    nested++;   
                else if (']' == *ptr)
                    nested--;
                if ('\0' == *ptr)
                {
                    DMSG(0, "MgenPattern::InitFromString(BURST) Error: non-terminated <patternParams>\n");
                    return false;
                }
            }
            ptr = strchr(ptr, ']');
            if ((*pptr != '[') || !ptr)
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) Error: missing <patternParams>\n");
                return false;   
            }
            if (!burst_pattern) 
            {
                if (!(burst_pattern = new MgenPattern()))
                {
                    DMSG(0, "MgenPattern::InitFromString(BURST) Error: pattern allocation: %s\n",
                            GetErrorString());
                    return false;
                }
            }
            strncpy(fieldBuffer, pptr+1, ptr - pptr - 1);
            fieldBuffer[ptr - pptr - 1] = '\0';
            if (!burst_pattern->InitFromString(patternType, fieldBuffer))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) Error: invalid <patternParams>\n");
                return false;    
            }
            // Set ptr to next field, skipping any white space
            ptr++;
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            if (1 != sscanf(ptr, "%s", fieldBuffer))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: missing burst duration type.\n");
                return false;
            }
            burst_duration_type = GetDurationTypeFromString(fieldBuffer);
            if (INVALID_DURATION == burst_duration_type)
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst duration type.\n");
                return false;
            }
            ptr += strlen(fieldBuffer);
            while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
            if (1 != sscanf(ptr, "%lf", &burst_duration_ave))
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst duration.\n");
                return false;
            }    
            if (burst_duration_ave < 0.0)
            {
                DMSG(0, "MgenPattern::InitFromString(BURST) error: invalid burst duration.\n");
                return false;
            }
            switch (burst_duration_type)
            {
                case FIXED:
                    burst_duration = burst_duration_ave;
                    break;
                case EXPONENTIAL:
                    burst_duration = ExponentialRand(burst_duration_ave);
                    break;
                case INVALID_DURATION:
                    ASSERT(0);  // can't get here
                    break;   
            } 
            interval_remainder = burst_duration; 
            last_time.tv_sec = last_time.tv_usec = 0;
            break;
        }
        case INVALID_TYPE:
            DMSG(0, "MgenPattern::InitFromString() unsupported pattern type\n");
            ASSERT(0);
            return false;        
    }  // end switch(type)
    return true;
}  // end MgenPattern::InitFromString()

double MgenPattern::GetPktInterval()
{ 
  switch (type)
  {
    case PERIODIC: 
         return interval_ave; 
    case POISSON: 
         return (interval_ave > 0.0) ? ExponentialRand(interval_ave) : interval_ave; 
    case JITTER:
    {
         double pktInterval = UniformRand(jitter_min, jitter_max);  
         double result = pktInterval + interval_remainder;
         interval_remainder = (interval_ave - pktInterval); 
         return result;
    }  
    case BURST:
    {
        // 
        double pktInterval = burst_pattern->GetPktInterval();
        if (pktInterval <= 0.0)
        {
            struct timeval currentTime;
            ProtoSystemTime(currentTime);
            if ((0 == last_time.tv_sec) && (0 == last_time.tv_usec)) last_time = currentTime;
            double deltaTime = (double)(currentTime.tv_sec - last_time.tv_sec);
            if (currentTime.tv_usec > last_time.tv_usec)
                deltaTime += 1.0e-06*(double)(currentTime.tv_usec - last_time.tv_usec);
            else
                deltaTime -= 1.0e-06*(double)(last_time.tv_usec - currentTime.tv_usec);
            if (interval_remainder > deltaTime)
            {
                last_time = currentTime;
                interval_remainder -= deltaTime;
                return pktInterval;   
            }
            last_time.tv_sec = last_time.tv_usec = 0;
        }
        else if (interval_remainder > pktInterval)
        {
            interval_remainder -= pktInterval;
            return pktInterval; 
        }
        // Prev burst has finished, schedule next burst
        double burstInterval = -1.0;
        switch (burst_type)
        {
            case REGULAR:
                burstInterval = interval_ave;
                break;
            case RANDOM:
                burstInterval = ExponentialRand(interval_ave);
                break;
            case INVALID_BURST:
                ASSERT(0);
                return burstInterval;   
        }
        if (burstInterval > burst_duration) 
            burstInterval = burstInterval - burst_duration + interval_remainder;
        else
            burstInterval = interval_remainder;
        if (burstInterval > pktInterval) pktInterval = burstInterval;
    
        // Now pick next burst duration.
        switch(burst_duration_type)
        {
            case FIXED:
                burst_duration = burst_duration_ave;
                break;
            case EXPONENTIAL:
                burst_duration = ExponentialRand(burst_duration_ave);
                break;
            case INVALID_DURATION:
                ASSERT(0);
                return -1.0;       
        }
        interval_remainder = burst_duration;
        return pktInterval;
    }
    case INVALID_TYPE:
        ASSERT(0);
        break;
  }  // end switch(type)
  return -1.0;
}  // end MgenPattern::GetPktInterval()

unsigned short MgenPattern::GetPktSize()
{
    switch (type)
    {
        case PERIODIC:
        case POISSON:
        case JITTER:
            return pkt_size;
        case BURST:
            return burst_pattern->GetPktSize();
        case INVALID_TYPE:
            ASSERT(0);
            break;
    }
    return 0;
}  // end MgenPattern::GetPktSize()


syntax highlighted by Code2HTML, v. 0.9.1