#!/usr/bin/env python

# WARNING: Work-in-progress

import os, math
import collections

ifile = "2016_08_17_LRPT_09-06-31.s" #"sample.s"
ofile = "sample.s.out"#"sample.s.out"
skipi = 0 #4800

'''
Sync Words Phased

0 = 00011010 11001111 11111100 00011101                                     = 0x1ACFFC1D
1 = 10001111 01100101 01010110 10000100                                     = 0x8F655684
2 = 11100101 00110000 00000011 11100010                                     = 0xE53003E2
3 = 01110000 10011010 10101001 01111011                                     = 0x709AA97B



10001111 01100101 01010110 10000100
01110000 10011010 10101001 01111011

'''

'''
Sync Word in 1/2 k=7 FEC:

1111 1100 1010 0010 1011 0110 0011 1101 1011 0000 0000 1101 1001 0111 1001 0100 0xfca2b63db00d9794
0101 0110 1111 1011 1101 0011 1001 0100 1101 1010 1010 0100 1100 0001 1100 0010 0x56fbd394daa4c1c2
0000 0011 0101 1101 0100 1001 1100 0010 0100 1111 1111 0010 0110 1000 0110 1011 0x035d49c24ff2686b
1010 1001 0000 0100 0010 1100 0110 1011 0010 0101 0101 1011 0011 1110 0011 1101 0xa9042c6b255b3e3d

also for IQ reversal, we reverse each pair of bits. Since I'm lazy, this script does the bit reversal over binary string:

x = "1010 1001 0000 0100 0010 1100 0110 1011 0010 0101 0101 1011 0011 1110 0011 1101".replace(" ", "")
o = ""
for i in range(0, len(x), 2):
  o += x[i+1]
  o += x[i]

print o
print hex(int(o, 2))

So for bit reversal we have this sync words:

1111 1100 0101 0001 0111 1001 0011 1110 0111 0000 0000 1110 0110 1011 0110 1000 0xfc51793e700e6b68
1010 1001 1111 0111 1110 0011 0110 1000 1110 0101 0101 1000 1100 0010 1100 0001 0xa9f7e368e558c2c1
0000 0011 1010 1110 1000 0110 1100 0001 1000 1111 1111 0001 1001 0100 1001 0111 0x03ae86c18ff19497
0101 0110 0000 1000 0001 1100 1001 0111 0001 1010 1010 0111 0011 1101 0011 1110 0x56081c971aa73d3e

'''

'''
Frame Size = 1024 bytes (1020 + 4)
Rate = 1/2
Frame Symbol Size = 1024 * 8 * 2

Input = IQ Byte. Symbol = 2 Byte
Frame Input Size = 1024 * 8 * 2 * 2
'''

frameSymbolSize = 1024 * 8
frameInputSize = frameSymbolSize * 2
frameBitSize = frameSymbolSize * 2
syncWordSize = 32
syncWordDoubleSize = 64

UW0 = 0xfca2b63db00d9794
UW1 = 0x56fbd394daa4c1c2
UW2 = 0x035d49c24ff2686b
UW3 = 0xa9042c6b255b3e3d

REVUW0 = 0xfc51793e700e6b68
REVUW1 = 0xa9f7e368e558c2c1
REVUW2 = 0x03ae86c18ff19497
REVUW3 = 0x56081c971aa73d3e

'''
  Tricky, we need a 2-bit group reversal for correcting IQ parameters.
  This could be done in several ways, but I will do as a Lookup Table.
  Basically for every index I in this element, there will be I with
  2-bit group reversed output. If you're curious how I got this values,
  I was lazy also.

def bit2rev(n):
  x = format(n, '#0{}b'.format(10))[2:] # Binary string of n
  o = "" # Reversed 2bit binary string
  for i in range(0, len(x), 2):
    o += x[i+1]
    o += x[i]
  return int(o, 2) # Return the integer

BIT2REV = []
for i in range(256):
  BIT2REV.append(bit2rev(i))


So just for example: Lets supose we get a byte with value 123 that is IQ reversed.
To un-reverse we go to the position 123 of the BIT2REV table that is: 183.

So if we get the binary strings:

123 = 01 11 10 11
186 = 10 11 01 11

So we inverted all 2 bit groups.
'''

BIT2REV = [
  0,     2,   1,   3,   8,  10,   9,  11,   4,   6,   5,   7,  12,  14,  13,  15,
  32,   34,  33,  35,  40,  42,  41,  43,  36,  38,  37,  39,  44,  46,  45,  47,
  16,   18,  17,  19,  24,  26,  25,  27,  20,  22,  21,  23,  28,  30,  29,  31,
  48,   50,  49,  51,  56,  58,  57,  59,  52,  54,  53,  55,  60,  62,  61,  63,
  128, 130, 129, 131, 136, 138, 137, 139, 132, 134, 133, 135, 140, 142, 141, 143,
  160, 162, 161, 163, 168, 170, 169, 171, 164, 166, 165, 167, 172, 174, 173, 175,
  144, 146, 145, 147, 152, 154, 153, 155, 148, 150, 149, 151, 156, 158, 157, 159,
  176, 178, 177, 179, 184, 186, 185, 187, 180, 182, 181, 183, 188, 190, 189, 191,
  64,   66,  65,  67,  72,  74,  73,  75,  68,  70,  69,  71,  76,  78,  77,  79,
  96,   98,  97,  99, 104, 106, 105, 107, 100, 102, 101, 103, 108, 110, 109, 111,
  80,   82,  81,  83,  88,  90,  89,  91,  84,  86,  85,  87,  92,  94,  93,  95,
  112, 114, 113, 115, 120, 122, 121, 123, 116, 118, 117, 119, 124, 126, 125, 127,
  192, 194, 193, 195, 200, 202, 201, 203, 196, 198, 197, 199, 204, 206, 205, 207,
  224, 226, 225, 227, 232, 234, 233, 235, 228, 230, 229, 231, 236, 238, 237, 239,
  208, 210, 209, 211, 216, 218, 217, 219, 212, 214, 213, 215, 220, 222, 221, 223,
  240, 242, 241, 243, 248, 250, 249, 251, 244, 246, 245, 247, 252, 254, 253, 255
]

'''
  So for phase ambiguities we can also use a LUT. So we have four cases, but we
  only need to account for one: 90 degrees. Because:
  0   deg is what we want
  90  deg we will do a LUT for getting 0 deg
  180 deg is the negate of 0 deg (so we can just xor by 0xFF)
  270 deg is the negate of 90 deg (so we can xor by 0xFF and then use 90 deg LUT)

  So a 90 deg rotation in the constelation maps as:
     0 deg => 90 deg
      00   =>   10
      01   =>   00
      11   =>   01
      10   =>   11

  So as usual:

def phaseshift(n):
  x = format(n, '#0{}b'.format(10))[2:] # Binary string of n
  o = "" # 90 deg phased binary string
  for i in range(0, len(x), 2):
    o += "1" if x[i+1] == "0" else "0"
    o += "0" if x[i] == "0" else "1"
  return int(o, 2) # Return the integer

PHASED = []
for i in range(256):
  PHASED.append(phaseshift(i))

'''

PHASED = [
  170, 168, 171, 169, 162, 160, 163, 161, 174, 172, 175, 173, 166, 164, 167, 165,
  138, 136, 139, 137, 130, 128, 131, 129, 142, 140, 143, 141, 134, 132, 135, 133,
  186, 184, 187, 185, 178, 176, 179, 177, 190, 188, 191, 189, 182, 180, 183, 181,
  154, 152, 155, 153, 146, 144, 147, 145, 158, 156, 159, 157, 150, 148, 151, 149,
   42,  40,  43,  41,  34,  32,  35,  33,  46,  44,  47,  45,  38,  36,  39,  37,
   10,   8,  11,   9,   2,   0,   3,   1,  14,  12,  15,  13,   6,   4,   7,   5,
   58,  56,  59,  57,  50,  48,  51,  49,  62,  60,  63,  61,  54,  52,  55,  53,
   26,  24,  27,  25,  18,  16,  19,  17,  30,  28,  31,  29,  22,  20,  23,  21,
  234, 232, 235, 233, 226, 224, 227, 225, 238, 236, 239, 237, 230, 228, 231, 229,
  202, 200, 203, 201, 194, 192, 195, 193, 206, 204, 207, 205, 198, 196, 199, 197,
  250, 248, 251, 249, 242, 240, 243, 241, 254, 252, 255, 253, 246, 244, 247, 245,
  218, 216, 219, 217, 210, 208, 211, 209, 222, 220, 223, 221, 214, 212, 215, 213,
  106, 104, 107, 105,  98,  96,  99,  97, 110, 108, 111, 109, 102, 100, 103, 101,
   74,  72,  75,  73,  66,  64,  67,  65,  78,  76,  79,  77,  70,  68,  71,  69,
  122, 120, 123, 121, 114, 112, 115, 113, 126, 124, 127, 125, 118, 116, 119, 117,
   90,  88,  91,  89,  82,  80,  83,  81,  94,  92,  95,  93,  86,  84,  87,  85
]


def binary(num, length=8):
    return format(num, '#0{}b'.format(length + 2))

def binStringToIntSoftArr(b):
  if b[1] == 'b':
    b = b[2:]
  a = []
  for i in b:
    a.append(int(i) * 0xFF)
  return a

def checkCorrelationInBuffer():
  uw0mc = 0 # Highest Correlation Factor for UW0
  uw0p  = 0 # Highest Correlation Position for UW0
  uw1mc = 0
  uw1p  = 0
  uw2mc = 0
  uw2p  = 0
  uw3mc = 0
  uw3p  = 0
  ruw0mc = 0 # Highest Correlation Factor for REVUW0
  ruw0p  = 0 # Highest Correlation Position for REVUW0
  ruw1mc = 0
  ruw1p  = 0
  ruw2mc = 0
  ruw2p  = 0
  ruw3mc = 0
  ruw3p  = 0

  for i in range(0, frameBitSize - syncWordDoubleSize):
    uw0c = 0
    uw1c = 0
    uw2c = 0
    uw3c = 0
    ruw0c = 0
    ruw1c = 0
    ruw2c = 0
    ruw3c = 0
    # To get the highest correlation, we xor with the word.
    for k in range(0, syncWordDoubleSize):
      uw0c += sbits[i+k] ^ UW0[k]
      uw1c += sbits[i+k] ^ UW1[k]
      uw2c += sbits[i+k] ^ UW2[k]
      uw3c += sbits[i+k] ^ UW3[k]
      ruw0c += sbits[i+k] ^ REVUW0[k]
      ruw1c += sbits[i+k] ^ REVUW1[k]
      ruw2c += sbits[i+k] ^ REVUW2[k]
      ruw3c += sbits[i+k] ^ REVUW3[k]

    uw0p = i if uw0c > uw0mc else uw0p
    uw1p = i if uw1c > uw1mc else uw1p
    uw2p = i if uw2c > uw2mc else uw2p
    uw3p = i if uw3c > uw3mc else uw3p
    ruw0p = i if ruw0c > ruw0mc else ruw0p
    ruw1p = i if ruw1c > ruw1mc else ruw1p
    ruw2p = i if ruw2c > ruw2mc else ruw2p
    ruw3p = i if ruw3c > ruw3mc else ruw3p

    uw0mc = uw0c if uw0c > uw0mc else uw0mc
    uw1mc = uw1c if uw1c > uw1mc else uw1mc
    uw2mc = uw2c if uw2c > uw2mc else uw2mc
    uw3mc = uw3c if uw3c > uw3mc else uw3mc
    ruw0mc = ruw0c if ruw0c > ruw0mc else ruw0mc
    ruw1mc = ruw1c if ruw1c > ruw1mc else ruw1mc
    ruw2mc = ruw2c if ruw2c > ruw2mc else ruw2mc
    ruw3mc = ruw3c if ruw3c > ruw3mc else ruw3mc

  return uw0p, uw0mc, uw1p, uw1mc, uw2p, uw2mc, uw3p, uw3mc, ruw0p, ruw0mc, ruw1p, ruw1mc, ruw2p, ruw2mc, ruw3p, ruw3mc



UW0 = binStringToIntSoftArr(binary(UW0, syncWordDoubleSize))
UW1 = binStringToIntSoftArr(binary(UW1, syncWordDoubleSize))
UW2 = binStringToIntSoftArr(binary(UW2, syncWordDoubleSize))
UW3 = binStringToIntSoftArr(binary(UW3, syncWordDoubleSize))
REVUW0 = binStringToIntSoftArr(binary(REVUW0, syncWordDoubleSize))
REVUW1 = binStringToIntSoftArr(binary(REVUW1, syncWordDoubleSize))
REVUW2 = binStringToIntSoftArr(binary(REVUW2, syncWordDoubleSize))
REVUW3 = binStringToIntSoftArr(binary(REVUW3, syncWordDoubleSize))

f = open(ifile, "r")
fsize = os.path.getsize(ifile)
#o = open(ofile, "w")
#ob = open(ofile + "bit", "w")

sbits = collections.deque(maxlen=frameBitSize)

count = 0
fcount = 0

f.seek(skipi)
count += skipi

print "Frame Input Size: %s" %frameInputSize

while count < fsize:
  '''
    Read the data from input to sbits
  '''
  data = f.read(frameInputSize)
  for i in range(0, frameInputSize):
  	sbits.append(ord(data[i]))

  '''
    Search for the sync word using correlation
  '''

  uw0p, uw0mc, uw1p, uw1mc, uw2p, uw2mc, uw3p, uw3mc, ruw0p, ruw0mc, ruw1p, ruw1mc, ruw2p, ruw2mc, ruw3p, ruw3mc = checkCorrelationInBuffer()
  mp = max(uw0mc, uw1mc, uw2mc, uw3mc, ruw0mc, ruw1mc, ruw2mc, ruw3mc)

  print "Frame: #%s" %fcount
  print "Max Correlation: %s" % (mp / 256)
  if mp == uw0mc:
    print "Max Correlation with 0   degrees Word at %s" % uw0p
    n = 0
    p = uw0p
  elif mp == uw1mc:
    print "Max Correlation with 90  degrees Word at %s" % uw1p
    n = 1
    p = uw1p
  elif mp == uw2mc:
    print "Max Correlation with 180 degrees Word at %s" % uw2p
    n = 2
    p = uw2p
  elif mp == uw3mc:
    print "Max Correlation with 270 degrees Word at %s" % uw3p
    n = 3
    p = uw3p
  elif mp == ruw0mc:
    print "Max Correlation with 0   degrees Word at %s Reverse IQ" % ruw0p
    n = 0
    p = ruw0p
  elif mp == ruw1mc:
    print "Max Correlation with 90  degrees Word at %s Reverse IQ" % ruw1p
    n = 1
    p = ruw1p
  elif mp == ruw2mc:
    print "Max Correlation with 180 degrees Word at %s Reverse IQ" % ruw2p
    n = 2
    p = ruw2p
  elif mp == ruw3mc:
    print "Max Correlation with 270 degrees Word at %s Reverse IQ" % ruw3p
    n = 3
    p = ruw3p

  if mp / 256 < 40:
    print "Frame Lock Error. Correlation less than 47 bits"
    count += frameInputSize
  else:
    '''
      Read p bits for syncing the Circle Buffer
      Each pair of bits come from 2 bytes of the input
      So we will read 1 byte per bit
    '''
    #p -= 12
    t = p if p % 2 == 0 else p + 1
    data = f.read(t)
    for i in range(0, t):
      sbits.append(ord(data[i]))

    '''
      Now we should have everything in sync
    '''
    #frame = processFrame(list(sbits), n)
    #o.write(frame)
    #frameb = processFrameBitMap(list(sbits), n)
    #ob.write(frameb)
    fcount += 1
    count += frameInputSize + t

'''
  Clean everything
'''

f.close()
o.close()
ob.close()