# HC_PINS_Sullivan.py - Hill Climbing - search Pins - Lugs are known
# Algorith: the Sullivan's one

import string
import sys
import random
import math
import HC_SCORE
from bib_util import *
import m209
import getopt
import time

theOverlaps = [
 [0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
 [1, 0], [1, 5], [1, 6], [1, 7], [0, 8],
 [2, 1], [2, 5], [2, 9], [2,10], [2,11],
 [3, 2], [3, 6], [3, 9], [3,12], [3,13],
 [4, 3], [4, 7], [4,10], [4,12], [4,14],
 [5, 4], [5, 8], [5,11], [5,13], [5,14]
]

#======================================
def initKeyPin( nbActivePins ):
    currentKeyPin= ""
    pins_array = [1] * nbActivePins
    pins_array.extend( [0] * (131 - nbActivePins) )
    random.shuffle( pins_array )
    pins_array = map( str, pins_array)
    return ''.join( pins_array )
    
#======================================
def usage():
	print "usage:"
	print "  -c crypto	The cryptogram"
	print "  -M method	The method (IC, UNI, BIG,TRI,...), default: IC"
	print "  -m file_method	The file used with method"
	print "  -l letter	The letter (shift, default: Z"
	print "  -L lugs	The lugs (ex: 1:5:4:13:2:7)"
	print "  -O overlaps	The overlaps (ex: 0:0:1...:1:1)"
	print "  -t tries 	Number of tries, default: 125"
#======================================

#--------------------------------------------------
try:
  opts, args = getopt.getopt(sys.argv[1:], 
	"ho:c:M:m:t:l:L:O:v", ["help", "output=", "crypto=","method=",
	"method_file=", "tries=", "letter=", "lugs=", "overlaps=", "verbose"])
except getopt.GetoptError as err:
  # print help information and exit:
  print sys.argv[0],": ", str(err) # option -a not recognized"
  usage()
  sys.exit(2)

output = None
verbose = False
MAX_Z = 125
METHOD = "IC"
file_method = ""
msg = ""
PINS = ""
LUGS = []
OVER = [0]*15
cle_letter_shift = "Z"

for o, a in opts:
  if o == "-v":
    verbose = True
  elif o in ("-h", "--help"):
    usage()
    sys.exit()
  elif o in ("-o", "--output"):
    output = a
  elif o in ("-c", "--crypto"):
    msg = litfic( a )
  elif o in ("-M", "--method"):
    METHOD = a
  elif o in ("-m", "--method_file"):
    file_method = a
  elif o in ("-t", "--tries"):
    MAX_Z = int(a)
  elif o in ("-l", "--letter"):
    cle_letter_shift = a
  elif o in ("-L", "--lugs"):
    LUGS=a.split(":")
    cle_lug=list( map(int,LUGS) )
  elif o in ("-O", "--overlaps="):
    tb = a.split(":")
    OVER = map(int,tb)
  else:
    assert False, "unhandled option"
if msg == "" or file_method == "":
  usage()
  sys.exit(1)
HC_SCORE.score_init[METHOD]( file_method )
#--------------------------------------------------
e = m209.Hagelin ( cle_letter_shift, 0 )
MAX_SCORE = 0
start_time = time.time()
nod = 0 # number of decrypts
currentKeyLug = cle_lug
oldKeyLug = currentKeyLug
currentKeyOve = OVER
oldKeyOve = currentKeyOve 
e.setLugs( currentKeyLug )
e.setOver( currentKeyOve )

# 1. Randomly set 40% of the pins in ARRAY to the active state.
# 2. Loop while the number of active pins in ARRAY is less than 60%
#    Remark: 40% == 52 pins, 60% == 80 pins
for nbActivePins in range(52,81):
  print "====> Number of Active Pins: ", nbActivePins
  # 3. Decrypt the message, using ARRAY and obtain a score for this 
  #    pin pattern
  # 3.1. Set the external wheels settings to AAAAAA
  e.rewind()

  # 3.2. Generate initial random pins 
  ARRAY = initKeyPin(nbActivePins)

  # 3.3 Save a copy of ARRAY in SAVE
  SAVE = ARRAY

  # 3.4 Configure key 
  e.setAllPinsAllW( ARRAY )

  # 3.5 Decipher with current key
  plain = m209.chiffre( e, msg ) ; nod += 1

  # 3.6 Calculate fitness 
  HIGH_SCORE = HC_SCORE.score[METHOD]( plain )
  MAX_CLAIR = plain

  # 4. Swap an score all pin pairs in ARRAY. Retain only the swap that
  #    increase the score. Record any higher score in a log file.
  # 5. Loop for typically 125 loops.
  for i in range( MAX_Z ): 
    k1 = 0 ; k2 = 1
    while k1 < 129 or k2 < 130:
      # --- External Key sets to "AAAAAA"
      e.rewind()

      # --- Swap a pair of pins
      tb = list( SAVE )
      p1 = k1
      p2 = k2
      k2 = k2 + 1 
      if k2 == 130 : k1 = k1 + 1 ; k2 = k1 + 1
      if tb[p1] == tb[p2]: continue
      x = tb[p1]
      tb[p1] = tb[p2]
      tb[p2] = x
      ARRAY = "".join( tb )
      e.setAllPinsAllW( ARRAY )

      # --- To cipher
      plain = m209.chiffre( e, msg ) ; nod += 1

      # --- Calculate fitness
      score = HC_SCORE.score[METHOD]( plain )

      # --- Keep it if best
      if score > HIGH_SCORE :
        HIGH_SCORE = score			
        SAVE = ARRAY
        MAX_CLAIR = str(plain)

      # ... Record any higher score in a log file.
      if HIGH_SCORE > MAX_SCORE :
        elaps = time.time() - start_time
        MAX_SCORE = HIGH_SCORE
        print ">>>MAX SCORE: ", HIGH_SCORE, "==", MAX_CLAIR[0:50]
        print "CLE: ", SAVE, oldKeyLug, oldKeyOve, nod, elaps, 
        print nbActivePins, i, k1, k2

    # 6. Introduce some small random variation in ARRAY, keeping
    #    the number of active pins constant.
    for j in range(0):
      tb = list( SAVE )
      i1 = random.randint(0,130)
      i2 = random.randint(0,130)
      if i1 == i2 : continue    
      if tb[i1] == "1" : x = "0"
      else: x = "1"
      tb[ i1 ] = x
      if tb[i2] == "1" : x = "0"
      else: x = "1"
      tb[ i2 ] = x
      SAVE = "".join( tb )

