from __future__ import print_function
from pprint import pformat
import json
import logging
import socket
import six
import ssl
import sys
try:
    import configparser
except:
    from six.moves import configparser
    
if sys.version_info[0] < 3: #fix more python 3 incompatibilities
    input = raw_input

log = logging.getLogger(__name__)

class Password(object):
    '''
    Get Roomba blid and password - only V2 firmware supported
    if IP is not supplied, class will attempt to discover the Roomba IP first.
    Results are written to a config file, default ".\config.ini"
    V 1.2.3 NW 9/10/2018 added support for Roomba i7
    V 1.2.5 NW 7/10/2019 changed PROTOCOL_TLSv1 to PROTOCOL_TLS to fix i7 software connection problem
    V 1.2.6 NW 12/11/2019 add cipher to ssl to avoid dh_key_too_small issue
    '''

    VERSION = __version__ = "1.2.6"

    def __init__(self, address='255.255.255.255', file=".\config.ini"):
        self.address = address
        self.file = file
        #self.log = logging.getLogger(__name__+'.Roomba_getpassword')
        if __name__ == "password": 
            self.log = logging.getLogger("__main__")    #another logging fix NW 3/2/2018
        else:
            self.log = logging.getLogger("roomba.__main__")
        #self.log.info("__name__ is %s" % __name__)
        self.log.info("Using Password version %s" % self.VERSION)
        self.get_password()

    def receive_udp(self):
        #set up UDP socket to receive data from robot
        port = 5678
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(10)
        if self.address == '255.255.255.255':
            s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        s.bind(("", port))  #bind all intefaces to port
        print("waiting on port: %d for data" % port)
        message = 'irobotmcs'
        s.sendto(message.encode(), (self.address, port))
        roomba_dict = {}
        while True:
            try:
                udp_data, addr = s.recvfrom(1024)   #wait for udp data
                #print('Received: Robot addr: %s Data: %s ' % (addr, udp_data))
                if len(udp_data) > 0:
                    if udp_data != message:
                        try:
                            if self.address != addr[0]:
                                self.log.warn(
                                    "supplied address %s does not match "
                                    "discovered address %s, using discovered "
                                    "address..." % (self.address, addr[0]))
                            if udp_data.decode() != message:
                                parsedMsg = json.loads(udp_data.decode()) #1.2.3 added .decode() to avoid python 3 bytes error
                                roomba_dict[addr]=parsedMsg
                        except Exception as e:
                            print("json decode error: %s" % e)
                            print('RECEIVED: %s', pformat(udp_data))
                        # print('Robot Data: %s '
                        #       % json.dumps(parsedMsg, indent=2))
                else:
                    break
            except socket.timeout:
                break
        s.close()
        return roomba_dict

    def get_password(self):
        import struct
        #get roomba info
        blid=None
        roombas = self.receive_udp()

        if len(roombas) == 0:
            print("No Roombas found, try again...")
            return False
        else:
            print("found %d Roomba(s)" % len(roombas))

        for address,parsedMsg in six.iteritems(roombas):
            addr = address[0]
            if int(parsedMsg["ver"]) < 2:
                print("Roombas at address: %s does not have the correct "
                      "firmware version. Your version info is: %s"
                      % (addr,json.dumps(parsedMsg, indent=2)))
                continue

            print("Make sure your robot (%s) at IP %s is on the Home Base and "
                  "powered on (green lights on). Then press and hold the HOME "
                  "button on your robot until it plays a series of tones "
                  "(about 2 seconds). Release the button and your robot will "
                  "flash WIFI light."
                  % (parsedMsg["robotname"],addr))
            input("Press Enter to continue...")

            print("Received: %s"  % json.dumps(parsedMsg, indent=2))
            print("\r\rRoomba (%s) IP address is: %s"
                  % (parsedMsg["robotname"],addr))
            hostname = parsedMsg["hostname"].split('-')
            if hostname[0] == 'Roomba' or hostname[0] == 'iRobot':  #for i7 robot name is now iRobot
                blid = hostname[1]

            if hasattr(str, 'decode'):
                # this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = 'f005efcc3b2900'.decode("hex")
            else:
                #this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = bytes.fromhex('f005efcc3b2900')
            #send socket
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(10)

            #ssl wrap
            wrappedSocket = ssl.wrap_socket(
                sock, ssl_version=ssl.PROTOCOL_TLS, ciphers='DEFAULT@SECLEVEL=1')   #ciphers='HIGH:!DH:!aNULL' may work as well
            #connect and send packet
            try:
                wrappedSocket.connect((addr, 8883))
            except Exception as e:
                print("Connection Error %s" % e)

            wrappedSocket.send(packet)
            data = b''
            data_len = 35
            while True:
                try:
                    # NOTE data is 0xf0 (mqtt RESERVED) length (0x23 = 35),
                    # 0xefcc3b2900 (magic packet), 0xXXXX... (30 bytes of
                    # password). so 7 bytes, followed by 30 bytes of password
                    # (total of 37)
                    if len(data) >= data_len+2:
                        break
                    data_received = wrappedSocket.recv(1024)
                except socket.error as e:
                    print("Socket Error: %s" % e)
                    break

                if len(data_received) == 0:
                    print("socket closed")
                    break
                else:
                    data += data_received
                    if len(data) >= 2:
                        data_len = struct.unpack("B", data[1:2])[0]

            #close socket
            wrappedSocket.close()
            # if len(data) > 0:
            #     import binascii
            #     print("received data: hex: %s, length: %d"
            #           % (binascii.hexlify(data), len(data)))
            if len(data) <= 7:
                print('Error getting password, receive %d bytes. Follow the '
                      'instructions and try again.' % len(data))
                return False
            else:
                # Convert password to str
                #password = str(data[7:].decode()) #old version
                password = str(data[7:].decode().rstrip('\x00')) #for i7 - has null termination
                print("blid is: %s" % blid)
                print('Password=> %s <= Yes, all this string.' % password)
                print('Use these credentials in roomba.py')

                Config = configparser.ConfigParser()
                Config.add_section(addr)
                Config.set(addr,'blid', blid)
                Config.set(addr,'password', password)
                Config.set(addr,'data', pformat(parsedMsg))
                # write config file
                with open(self.file, 'w') as cfgfile:
                    Config.write(cfgfile)
        return True