'''
Created on Dec 7, 2011
Modified on Mar 11, 2012
@version: 0.2
@author: Dilum Bandara
@email: dilumb@engr.colostate.edu
@license: Apache License v2.0

   Copyright 2012 H. M. N. Dilum Bandara and Anura P. Jayasumana, Colorado State
   University

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''

'''
Generate queries for CCN-Data Fusion simulator
'''

import random
import math
import sys
import copy
from operator import itemgetter


#Users. Suppose NWS & EM users are placed within a grid & can be at any random 
#location within grid box. Others users are randomly deployed within entire 
#simulation area
numNWS = [2, 1] #number of NWS users [divide x-axis, divide y-axis]
numEM = [6, 5] #number of EMs
numRES = 8 #Researches
numMED = 20 #Media
numPUB = 0 #General public

genBaseTime = 300 #Starting time to generate queries }
genEndTime = 14400 + genBaseTime #Time for last query
numRadarsX = 11 #Number of radars along X-axis
numRadarsY = 11 #Number of radars along y-axis
intRadarDis = 30000 #30 km
queryRes = 6000 #Minimum size of query(also use to round query boundaries)
#EMs are interested in weather events that are happening x-km away
expandAOIBy = 0

#Area where highly active weather is present
#Location of weather event. {time: event boundary (x1, y1) & (x2, y2)
'''
eventLocWithTime = {0: [[50000, 30000, 100000, 75000], [5000, 10000, 10000, 15000]], \
                    240: [[50000, 35000, 110000, 80000], [6000, 11000, 11000, 16000]],\
                    360: [[50000, 35000, 120000, 85000]]}
'''
#480x420 km^2 area
'''
eventLocWithTime = {300: [[109474, 266341, 480000, 420000], \
                          [33684, 40976, 117895, 245854]], \
                    2100: [[117895, 266341, 480000, 420000], \
                           [46316, 56341, 130526, 235610]],
                    3900: [[126316, 266341, 480000, 420000], \
                           [58947, 71707, 143158, 225366]],
                    5700: [[126316, 266341, 480000, 420000], \
                           [71579, 61463, 155789, 215122]],
                    7500: [[126316, 266341, 480000, 420000], \
                           [84211, 51220, 168421, 204878]], \
                    9300: [[117895, 250976, 480000, 420000]],\
                    11100: [[109474, 235610, 480000, 420000]], \
                    12900: [[96842, 215122, 480000, 414878]], \
                    14700: [[84211, 194634, 480000, 409756]], \
                    16500: [[96842, 199756, 480000, 379024]], \
                    18300: [[109474, 204878, 480000, 348293]], \
                    20100: [[143158, 184390, 480000, 343171]], \
                    21900: [[176842, 163902, 480000, 338049]]}
'''
#4280x240 km^2 area
'''
eventLocWithTime = {300: [[49474, 86341, 420000, 240000], \
                          [0, 0, 57895, 65854]], \
                    2100: [[57895, 86341, 420000, 240000], \
                           [0, 0, 70526, 55610]],
                    3900: [[66316, 86341, 420000, 240000], \
                           [0, 0, 83158, 45366]],
                    5700: [[66316, 86341, 420000, 240000], \
                           [11579, 0, 95789, 35122]],
                    7500: [[66316, 86341, 420000, 240000], \
                           [24211, 0, 108421, 24878]], \
                    9300: [[57895, 70976, 420000, 240000]],\
                    11100: [[49474, 55610, 420000, 240000]], \
                    12900: [[36842, 35122, 420000, 234878]], \
                    14700: [[24211, 14634, 420000, 229756]], \
                    16500: [[36842, 19756, 420000, 199024]], \
                    18300: [[49474, 24878, 420000, 168293]], \
                    20100: [[83158, 4390, 420000, 163171]], \
                    21900: [[116842, 0, 420000, 158049]]} 
'''
#270x270
'''
eventLocWithTime = {300: [[42188, 135000, 270000, 270000], \
                          [42188, 10385, 84375, 72692]], \
                    2100: [[50625, 129808, 270000, 270000], \
                           [25313, 5192, 71719, 77885]],
                    3900: [[59063, 124615, 270000, 270000], \
                           [8438, 0, 59063, 83077]],
                    5700: [[42188, 124615, 236250, 270000], \
                           [4219, 31154, 46406, 93462]],
                    7500: [[25313, 124615, 202500, 270000], \
                           [0, 62308, 33750, 103846]], \
                    9300: [[25313, 114231, 236250, 254423]],\
                    11100: [[25313, 103846, 270000, 238846]], \
                    12900: [[12656, 109038, 270000, 218077]], \
                    14700: [[0, 114231, 270000, 197308]]} 
'''
#300x300
eventLocWithTime = {300: [[33333, 144828, 208333, 300000], \
                          [0, 20690, 41667, 124138]], \
                    2100: [[45833, 144828, 254167, 300000], \
                           [0, 15517, 54167, 113793]],
                    3900: [[58333, 144828, 300000, 300000], \
                           [0, 10345, 66667, 103448]],
                    5700: [[54167, 144828, 300000, 300000], \
                           [25000, 15517, 116667, 93103]],
                    7500: [[50000, 144828, 300000, 300000], \
                           [50000, 20690, 166667, 82759]], \
                    9300: [[41667, 134483, 300000, 279310],\
                           [125000, 10345, 208333, 56897]],
                    11100: [[33333, 124138, 300000, 258621], \
                            [200000, 0, 250000, 31034]],
                    12900: [[20833, 129310, 300000, 237931]], \
                    14700: [[8333, 134483, 300000, 217241]]} 
#Data generation intervals for users & data type for their Area of Interest (AoI) 
#general public may only ask REFL & DDOP
genInterAOI = {'NWS': {'REFL': 60, 'DDOP': 60, 'NBRR': 60, 'NCAS':60, 'QPE': 60},\
           'EM': {'REFL': 60, 'DDOP': 60, 'NBRR': 120, 'NCAS': 120, 'QPE': 120},\
           'RES' : {'REFL': 60, 'DDOP': 30, 'NBRR': 60, 'NCAS': 60, 'QPE': 60},\
           'MED': {'REFL': 60, 'DDOP': 60, 'NBRR': 120, 'NCAS': 120, 'QPE': 120},\
           'PUB': {'REFL': 300, 'DDOP': 300}}
#Data generation intervals if user(s) wants entire sensor field
genIntSensorField = {'RES': {'REFL': 300, 'DDOP': 300}, \
                     'MED': {'REFL': 300, 'DDOP': 300}}

fOut = 'queries_tmp.txt'
queries = []


def alignAOI(userAOI, sensorFieldX, sensorFieldY):
    '''
    Round query boundary based on resolution. Otherwise 2 queries may not
    overlap even when their area of interest (AOI) is slightly different
    @param userAOI: [Lower left edge x-axis, Lower left edge y-axis value, 
    Upper Right edge x-axis value, Upper Right edge x-axis value]
    @return: [minX, minY, maxX, maxY] 
    '''
    minX = userAOI[0]
    minY = userAOI[1]
    maxX = userAOI[2]
    maxY = userAOI[3]
    if minX % queryRes != 0: 
        minX = int(math.floor(minX/float(queryRes))) * queryRes
    if minY % queryRes != 0: 
        minY = int(math.floor(minY/float(queryRes))) * queryRes
    if maxX % queryRes != 0: 
        maxX = int(math.ceil(maxX/float(queryRes))) * queryRes
    if maxY % queryRes != 0: 
        maxY = int(math.ceil(maxY/float(queryRes))) * queryRes
    if maxX > sensorFieldX: maxX = sensorFieldX
    if maxY > sensorFieldY: maxY = sensorFieldY
    return [minX, minY, maxX, maxY]


def placeNodeRnd(AOI):
    '''
    Place node randomly within given boundary/AOI
    @param AOI: [minX, minY, maxX, maxY] of boundary
    @return: Node location [x, y] 
    '''
    return [random.randint(AOI[0], AOI[2]), random.randint(AOI[1], AOI[3])]


def AOIwithinEvent(userAOI, eventLoc):
    '''
    AOI for user within active weather event
    @param userAOI: User's AOI
    @return: [new AOI] or [] if not within weather event
    '''
    #Is user's AOI within active event?
    if (userAOI[2] <= eventLoc[0]) or (userAOI[0] >= eventLoc[2])\
            or (userAOI[3] <= eventLoc[1]) or \
            (userAOI[1] >= eventLoc[3]):
        return []
    minX = max(userAOI[0], eventLoc[0]) #new AOI
    minY = max(userAOI[1], eventLoc[1])
    maxX = min(userAOI[2], eventLoc[2])
    maxY = min(userAOI[3], eventLoc[3])
    return [minX, minY, maxX, maxY]
    
    
def genQuerySeq(userType, userID, userLoc, userAOI, eventLoc, sensorFieldX, \
                sensorFieldY):
    '''
    Generate all queries for given node. Query format
    <user ID, user location (x), user location (y), query time, content need to 
    be fresh than last t seconds, query string>
    @param userType: Type of user 
    @param userID:
    @param userloc: Location of user
    @param userAOI: User's AOI
    @param eventLoc: Location of active weather event(s)
    @param sensorFieldX: Size of sensor field (0, 0), to sensorField(x, y)  
    @param sensorFieldY: Size of sensor field (0, 0), to sensorField(x, y) 
    '''
    if userType == 'NWS' or userType == 'EM':
        for dataType in genInterAOI[userType]:
            if dataType == 'REFL' or dataType == 'DDOP':
                #Pick random time to ask first query after query generation time
                time = genBaseTime + random.randint(0, \
                                                genInterAOI[userType][dataType])
                newAOI = alignAOI(userAOI, sensorFieldX, sensorFieldY)
                queryAOI = '/' + str(newAOI[0]) + '/' + str(newAOI[1]) + '/' \
                            + str(newAOI[2]) + '/' + str(newAOI[3]) + '/'
                queries.append([userID, userLoc[0], userLoc[1], time,\
                                genInterAOI[userType][dataType]/2, \
                                queryAOI + dataType + '/', \
                                genInterAOI[userType][dataType]])
                lastTime = time
                while True: #Generate rest of the queries with same interval
                    time = lastTime + genInterAOI[userType][dataType]
                    if time > genEndTime: break
                    queries.append([userID, userLoc[0], userLoc[1], \
                                    time, genInterAOI[userType][dataType]/2,\
                                    queryAOI + dataType + '/', \
                                    genInterAOI[userType][dataType]])
                    lastTime = time
            else:            
                time = genBaseTime + random.randint(0, \
                                              genInterAOI[userType][dataType])                
                while True:
                    if time > genEndTime: break
                    nextTime = time + genInterAOI[userType][dataType]
                    idxTime = -1
                    for i in eventLoc: #Find largest matching time
                        if i > time: continue
                        elif i > idxTime: idxTime = i
                    if idxTime == -1:
                        time = nextTime 
                        continue                    
                    for j in range(len(eventLoc[idxTime])):                        
                        newAOI = AOIwithinEvent(userAOI, eventLoc[idxTime][j])
                        if len(newAOI) == 0: #Is user's AOI within active event? 
                            continue
                        newAOI = alignAOI(newAOI, sensorFieldX, sensorFieldY) #Alogn
                        queryAOI = '/' + str(newAOI[0]) + '/' + str(newAOI[1]) + \
                                '/' + str(newAOI[2]) + '/' + str(newAOI[3]) + '/'
                        queries.append([userID, userLoc[0], userLoc[1],\
                                        time, genInterAOI[userType][dataType]/2,\
                                        queryAOI + dataType + '/', \
                                        genInterAOI[userType][dataType]])
                    time = nextTime
    elif userType == 'RES' or userType == 'MED': #Scientist or media
        for dataType in genInterAOI[userType]:
            if dataType == 'REFL' or dataType == 'DDOP':
                time = genBaseTime + random.randint(0, \
                                                genInterAOI[userType][dataType])
                startTime = time
                while True:
                    if time > genEndTime: break
                    nextTime = time + genInterAOI[userType][dataType]
                    #Periodically ask for entire sensor field 
                    if (time - startTime) % genIntSensorField[userType][dataType]\
                                     == 0:
                        queryAOI = '/0/0/' + str(sensorFieldX) + '/' + \
                                str(sensorFieldY) + '/'
                        queries.append([userID, userLoc[0], userLoc[1],\
                                    time, genInterAOI[userType][dataType]/2,\
                                    queryAOI + dataType + '/', \
                                    genInterAOI[userType][dataType]])
                        time = nextTime
                        continue
                    #Else ask for entire region where weather is active
                    idxTime = -1
                    for i in eventLoc: #Find matching time
                        if i > time: continue
                        elif i > idxTime: idxTime = i            
                    if idxTime == -1:
                        time = nextTime 
                        continue
                    for j in range(len(eventLoc[idxTime])):
                        newAOI =  alignAOI(eventLoc[idxTime][j], sensorFieldX, \
                                           sensorFieldY)      
                        queryAOI = '/' + str(newAOI[0]) + '/' + str(newAOI[1]) \
                                    + '/' + str(newAOI[2]) + '/' + \
                                    str(newAOI[3]) + '/'
                        queries.append([userID, userLoc[0], userLoc[1],\
                                    time, genInterAOI[userType][dataType]/2, \
                                    queryAOI + dataType + '/', \
                                    genInterAOI[userType][dataType]])
                    time = nextTime
            else:
                time = genBaseTime + random.randint(0, \
                                                genInterAOI[userType][dataType])
                while True:
                    if time > genEndTime: break
                    nextTime = time + genInterAOI[userType][dataType]
                    #Ask for entire region where weather is active
                    idxTime = -1
                    for i in eventLoc: #Find matching time
                        if i > time: continue
                        elif i > idxTime: idxTime = i    
                    if idxTime == -1:
                        time = nextTime 
                        continue
                    for j in range(len(eventLoc[idxTime])):                        
                        newAOI = alignAOI(eventLoc[idxTime][j], sensorFieldX, \
                                          sensorFieldY)
                        queryAOI = '/' + str(newAOI[0]) + '/' + str(newAOI[1]) +\
                                '/'  + str(newAOI[2]) + '/' + str(newAOI[3]) + '/'
                        queries.append([userID, userLoc[0], userLoc[1],\
                                    time, genInterAOI[userType][dataType]/2, \
                                    queryAOI + dataType + '/', \
                                    genInterAOI[userType][dataType]])
                    time = nextTime
    else: #General public
        print 'Query generation for general public not implemented'


def genQueries(sensorFieldX, sensorFieldY):
    '''
    Generate queries for area(s) of interest
    query format [user ID, user location(x, y), query time, query]
    @param sensorFieldX: Size of sensor field (0, 0), to sensorField(x, y)  
    @param sensorFieldY: Size of sensor field (0, 0), to sensorField(x, y)
    '''
    userID = 0
    expandedEventLoc = copy.deepcopy(eventLocWithTime)
    #NWS, EM, & Media are interested in events that are x-km away. Expand boundary 
    for i in expandedEventLoc:
        for j in range(len(expandedEventLoc[i])):
            expandedEventLoc[i][j][0] -= expandAOIBy
            if expandedEventLoc[i][j][0] < 0: expandedEventLoc[i][j][0] = 0
            expandedEventLoc[i][j][1] -= expandAOIBy
            if expandedEventLoc[i][j][1] < 0: expandedEventLoc[i][j][1] = 0
            expandedEventLoc[i][j][2] += expandAOIBy
            if expandedEventLoc[i][j][2] > sensorFieldX: 
                expandedEventLoc[i][j][2] = sensorFieldX
            expandedEventLoc[i][j][3] += expandAOIBy
            if expandedEventLoc[i][j][3] > sensorFieldY: 
                expandedEventLoc[i][j][3] = sensorFieldY 

    for k in genInterAOI: #For each user type
        print 'Generating queries for', k
        if k == 'NWS' or k == 'EM':
            widthX = 0
            widthY = 0
            numUserX = 0
            numUserY = 0
            if k == 'NWS':
                widthX = sensorFieldX/numNWS[0] #Width of a cell along x-axis
                widthY = sensorFieldY/numNWS[1] #Width of a cell along y-axis
                numUserX = numNWS[0]
                numUserY = numNWS[1]
            elif k == 'EM':
                widthX = sensorFieldX/numEM[0]
                widthY = sensorFieldY/numEM[1]
                numUserX = numEM[0]
                numUserY = numEM[1]
            for i in range(numUserX):
                for j in range(numUserY):
                    #Find AOI for given user & round it
                    userAOI = [i * widthX, j * widthY, \
                                    (i + 1) * widthX, (j + 1) * widthY]
                    #Place node randomly within AOI
                    genQuerySeq(k, userID, placeNodeRnd(userAOI), userAOI,\
                                expandedEventLoc, sensorFieldX, sensorFieldY)
                    userID += 1
        else: #Not NWS or EM
            numUser = 0
            if k == 'RES': numUser = numRES
            elif k == 'MED': numUser = numMED
            elif k == 'PUB': numUser = numPUB
            else: 
                print 'Unknown user type', k
                sys.exit(0)
            for i in range(numUser):
                #Place node randomly within AOI
                userAOI = [0, 0, sensorFieldX, sensorFieldY]
                if k == 'MED':
                    genQuerySeq(k, userID, placeNodeRnd(userAOI), userAOI,\
                                 expandedEventLoc, sensorFieldX, sensorFieldY)
                else:
                    genQuerySeq(k, userID, placeNodeRnd(userAOI), userAOI, \
                                eventLocWithTime, sensorFieldX, sensorFieldY)
                userID += 1
        print 'Generated queries for', k


def dumpQueries():
    '''
    First sort queries cronologically then dump to file
    '''
    sortedQueries = sorted(queries, key=itemgetter(3))
    fdOut = open(fOut, 'w')
    for i in range(len(sortedQueries)):
        fdOut.write(str(sortedQueries[i][0]) + '\t' + str(sortedQueries[i][1]) + \
                '\t' + str(sortedQueries[i][2]) + '\t' + str(sortedQueries[i][3])\
                + '\t' + str(sortedQueries[i][4]) + '\t' + sortedQueries[i][5] \
                + '\t' + str(sortedQueries[i][6]) + '\n')
    fdOut.close()


def main(*argv):
    '''
    Main function to indicate start of program
    '''
    if len(sys.argv) != 2:
        print 'Invalid argument. Enter python genQuery.py <random seed>'
        sys.exit(0)
    random.seed(sys.argv[1]) #Set seed
    sensorFieldX = (numRadarsX - 1) * intRadarDis #Length 
    sensorFieldY = (numRadarsY - 1) * intRadarDis #Width
    print 'Dimensions of sensor Field ' + str(sensorFieldX/1000) + ' x ' + \
                str(sensorFieldY/1000) + ' = ' + \
                str((sensorFieldX * sensorFieldY)/1000000) + ' km^2'
    #Test code
    #print alignAOI([2500, 3400, 12100, 20980])
    #print alignAOI([2500, 3400, 210001, 91000], sensorFieldX, sensorFieldY)
    #print placeNodeRnd([0, 0, 100, 100])
    #print placeNodeRnd([39, 51, 110, 200])
    #print AOIwithinEvent([0, 0, 100, 100], 0)
    #print AOIwithinEvent([100001, 150001, 100101, 150101], 0)
    #print AOIwithinEvent([60000, 0, 100000, 100], 0)
    #print AOIwithinEvent([60000, 150001, 100000, 200000], 0)
    #print AOIwithinEvent([40000, 80000, 90000, 100000], 0)
    #print AOIwithinEvent([40000, 90000, 50000, 100000], 0)
    #print AOIwithinEvent([40000, 70000, 60000, 110000], 0)
    #print AOIwithinEvent([60000, 120000, 90000, 140000], 0)
    
    #genQuerySeq('MED', 0, [10000, 21000], [0, 0, 105000, 45000], eventLocWithTime, sensorFieldX, sensorFieldY)
    #print queries

    genQueries(sensorFieldX, sensorFieldY)
    dumpQueries()
    print 'Done...'
    

if __name__ == '__main__':
    sys.exit(main(*sys.argv))
