def __init__(self, username, password, sqlalchemy_db, useragent=None):
     if useragent != None:
         self.dacbrowser = DACBrowser(username, password, useragent)
     else:
         self.dacbrowser = DACBrowser(username, password)
     self.sqlalchemy_db = sqlalchemy_db
     self.devices_url = 'https://developer.apple.com/account/ios/device/deviceList.action'
     self.devices_data = None
     self.devices_descriptions = None
     self.profiles_url = 'https://developer.apple.com/account/ios/profile/profileList.action'
     self.profile_download_url = 'https://developer.apple.com/account/ios/profile/profileContentDownload.action?displayId=%s'
     self.profiles_data = None
     self.profiles_descriptions = None
     self.certificates_url = 'https://developer.apple.com/account/ios/certificate/certificateList.action'
     self.certificates_data = None
     self.certificates_descriptions = None
     self.add_device_url = 'https://developer.apple.com/account/ios/device/deviceCreate.action'
     self.profile_edit_url = 'https://developer.apple.com/account/ios/profile/profileEdit.action'
     self.add_device_success_mesage = 'device with number success' # Fake message, I don't know right message
     self.add_device_already_exist = 'already exists'
     self.add_device_invalid = 'An invalid value (this is device number) was provided for the parameter'
     # An invalid value '5fd5ddcd7f3248c2b385d51c8548db4acd02f6e9 ' was provided for the parameter 'deviceNumber'."
     self.add_device_error = 'device with number' # Fake message, I don't know right message
     self.refresh_devices_descriptions_success = False
     self.refresh_profiles_descriptions_success = False
     self.url_error_detected = False
     self.url_error_had_occured = False
     self.required_csrf_http_code = 421
class AppleAccountProcessor(object):

    def __init__(self, username, password, sqlalchemy_db, useragent=None):
        if useragent != None:
            self.dacbrowser = DACBrowser(username, password, useragent)
        else:
            self.dacbrowser = DACBrowser(username, password)
        self.sqlalchemy_db = sqlalchemy_db
        self.devices_url = 'https://developer.apple.com/account/ios/device/deviceList.action'
        self.devices_data = None
        self.devices_descriptions = None
        self.profiles_url = 'https://developer.apple.com/account/ios/profile/profileList.action'
        self.profile_download_url = 'https://developer.apple.com/account/ios/profile/profileContentDownload.action?displayId=%s'
        self.profiles_data = None
        self.profiles_descriptions = None
        self.certificates_url = 'https://developer.apple.com/account/ios/certificate/certificateList.action'
        self.certificates_data = None
        self.certificates_descriptions = None
        self.add_device_url = 'https://developer.apple.com/account/ios/device/deviceCreate.action'
        self.profile_edit_url = 'https://developer.apple.com/account/ios/profile/profileEdit.action'
        self.add_device_success_mesage = 'device with number success' # Fake message, I don't know right message
        self.add_device_already_exist = 'already exists'
        self.add_device_invalid = 'An invalid value (this is device number) was provided for the parameter'
        # An invalid value '5fd5ddcd7f3248c2b385d51c8548db4acd02f6e9 ' was provided for the parameter 'deviceNumber'."
        self.add_device_error = 'device with number' # Fake message, I don't know right message
        self.refresh_devices_descriptions_success = False
        self.refresh_profiles_descriptions_success = False
        self.url_error_detected = False
        self.url_error_had_occured = False
        self.required_csrf_http_code = 421

    def set_devices_url(self, devices_url=None):
        if devices_url != None:
            self.devices_url = devices_url

    def set_profiles_url(self, profiles_url):
        if profiles_url != None:
            self.profiles_url = profiles_url

    def set_certificates_url(self, certificates_url):
        if certificates_url != None:
            self.certificates_url = certificates_url

    def extract_data_url_component(self, response_data, data_regex_pattern):
        data_url_component = None
        data_url_component_regex = re.compile(data_regex_pattern)
        regex_search_result = data_url_component_regex.search(response_data)
        if regex_search_result != None:
            data_url_component = regex_search_result.group(1)
        return data_url_component


    def get_data(self, data_url, post_params={}):
        data = None
        data_response = self.dacbrowser.post(data_url, post_params)
        #if data_response != None:
        if data_response.no_url_errors():
            data = json.loads(data_response.read())
            data_response.close()
        else:
            pass
            #print 'get_data, are url errors'
        return data

    def get_devices_data(self):
        response = self.dacbrowser.get(self.devices_url)
        #if response != None:
        if response.no_url_errors():
            print 'get_devices_data, no url errors'
            response_context = response.read()
            #print 'get_devices_data, response_context: ', response_context
            data_url = self.extract_data_url_component(response_context, 'deviceDataURL = "([^"]*)"')
            response.close()
            if data_url != None:
                self.devices_data = self.get_data(data_url)
            else:
                print 'get_devices_data, data_url is None'
        else:
            self.devices_data = None
            print 'get_devices_data, present url errors'
        return self.devices_data

    def get_devices_descriptions(self):
        self.get_devices_data()
        devices_data = self.devices_data
        if devices_data != None and devices_data != {}:
            self.devices_descriptions = [Device(device['deviceNumber'], device) for device in devices_data.get('devices')]
        return self.devices_descriptions

    def print_devices(self):
        devices_descriptions = self.devices_descriptions
        if devices_descriptions == None:
            devices_descriptions = self.get_devices_descriptions()
        for device in devices_descriptions:
            device.display()

    def get_provisioning_profiles_data(self):
        response = self.dacbrowser.get(self.profiles_url)
        if response.no_url_errors():
            data_url = self.extract_data_url_component(response.read(), 'profileDataURL = "([^"]*)"')
            print 'get_provisioning_profiles_data, data_url: ', data_url
            response.close()
            if data_url != None:
                self.profiles_data = self.get_data(data_url)
        else:
            print 'get_provisioning_profiles_data: are url errors'
            self.profiles_data = None
        return self.profiles_data

    def get_provisioning_profiles_descriptions(self):
        self.get_provisioning_profiles_data()
        profiles_data = self.profiles_data

        if profiles_data != None and profiles_data != {}:
            self.profiles_descriptions = [Profile(profile_description) for profile_description in profiles_data.get('provisioningProfiles')]
        return self.profiles_descriptions

    def download_provisioning_profile(self, url):
        profile_binary = None
        response = self.dacbrowser.get(url)
        #if response != None:
        if response.no_url_errors():
            profile_binary = response.read()
            response.close()
        return profile_binary

    def extract_devices_from_profile(self, profile_binary):
        devices_id_list = None
        profile_str = str(profile_binary)
        xml_begin_index = profile_str.find('<?xml')
        xml_end_index = profile_str.rfind('</plist>')
        profile_xml = profile_binary[xml_begin_index:xml_end_index + len('</plist>')]

        parser = etree.XMLParser()
        xml_tree = etree.parse(StringIO(profile_xml), parser)
        tag_keyword_elements = xml_tree.xpath('//key')
        for tag_keyword_element in tag_keyword_elements:
            if tag_keyword_element.text == 'ProvisionedDevices':
                succeeding_siblings = tag_keyword_element.itersiblings(preceding=False)
                devices_id_array = succeeding_siblings.next()
                devices_id_list = [id_string_element.text for id_string_element in devices_id_array.getchildren()]
        return devices_id_list
    
    def update_profile_and_device_tables(self):
        #sqlalchemy_session = self.sqlalchemy_db.session(expire_on_commit=False)
        sqlalchemy_session = self.sqlalchemy_db.session()
        present_devices = sqlalchemy_session.query(Device).all()
        present_devices_count = len(present_devices)

        retrieved_devices_count = -1
        new_devices_count = 0
        retrieved_profiles_count = -1
        new_profiles_count = 0
    
        url_error_detected = False
        devices_descriptions = self.get_devices_descriptions()
        if devices_descriptions == None:
            url_error_detected = True

        if url_error_detected == False:
            profiles_descriptions = self.get_provisioning_profiles_descriptions()
            if profiles_descriptions == None:
                url_error_detected == True

        all_downloaded_profiles_devices_numbers = set()
        profiles_with_associates_devices_numbers = list()

        #if self.refresh_profiles_descriptions_success and self.refresh_devices_descriptions_success:
        all_downloaded_profiles_devices_numbers_list = list()

        if url_error_detected == False:
            for profile in self.profiles_descriptions:
                profile_id = profile.id
                profile_devices_numbers = []
                profile_binary = self.download_provisioning_profile(self.profile_download_url % profile_id)
                if profile_binary != None:
                    profile.profile_binary = profile_binary
                    profile_devices_numbers = self.extract_devices_from_profile(profile_binary)
                    if profile_devices_numbers != None:
                        all_downloaded_profiles_devices_numbers.update(set(profile_devices_numbers))
                else:
                    url_error_detected = True
                    break
                profiles_with_associates_devices_numbers.append((profile, profile_devices_numbers))
            
            if url_error_detected == False:
                all_downloaded_profiles_devices_numbers_list = list(all_downloaded_profiles_devices_numbers)

        if url_error_detected == False:
            retrieved_devices_numbers = [device.number for device in self.devices_descriptions]
            retrieved_devices_count = len(retrieved_devices_numbers)

            # devices_delete_result = sqlalchemy_session.query(Device).filter(~Device.number.in_(retrieved_devices_numbers)).delete(synchronize_session='fetch')
            devices_to_delete = [device for device in present_devices if device.number not in retrieved_devices_numbers]
            for device_to_delete in devices_to_delete:
                sqlalchemy_session.delete(device_to_delete)
            sqlalchemy_session.commit()

            present_devices = sqlalchemy_session.query(Device).all()
            present_devices_numbers = [device.number for device in present_devices]

            new_devices = [device for device in self.devices_descriptions if device.number not in present_devices_numbers]
            new_devices_numbers = [device.number for device in new_devices]

            present_profiles = sqlalchemy_session.query(Profile).all()
            present_profiles_ids = [profile.id for profile in present_profiles]
            retrieved_profiles_ids = [profile.id for profile in self.profiles_descriptions]
            retrieved_profiles_count = len(retrieved_profiles_ids)

            profiles_to_delete = [profile for profile in present_profiles if profile.id not in retrieved_profiles_ids]
            for profile_to_delete in profiles_to_delete:
                sqlalchemy_session.delete(profile_to_delete)
            sqlalchemy_session.commit()

            present_profiles = sqlalchemy_session.query(Profile).all()
            present_profiles_ids = [profile.id for profile in present_profiles]

            new_devices_numbers_from_profiles = [device_number for device_number in all_downloaded_profiles_devices_numbers_list if (device_number not in present_devices_numbers and device_number not in new_devices_numbers)]

            new_devices_from_profiles = [Device(device_number) for device_number in new_devices_numbers_from_profiles]
            new_devices.extend(new_devices_from_profiles)

            sqlalchemy_session.add_all(new_devices)
            sqlalchemy_session.commit()
           
            devices_after_add = sqlalchemy_session.query(Device).all()
            
            new_profiles_with_associated_devices_numbers = [profile_numbers_pair for profile_numbers_pair in profiles_with_associates_devices_numbers if profile_numbers_pair[0].id not in present_profiles_ids]
            new_profiles_count = len(new_profiles_with_associated_devices_numbers)
            for present_profile in present_profiles:
                print 'Present profile id: ', present_profile.id
                for profile_numbers_pair in profiles_with_associates_devices_numbers:
                    retrieved_profile, devices_numbers = profile_numbers_pair
                    if present_profile.id == retrieved_profile.id:
                        if retrieved_profile.profile_binary != None:
                            present_profile.profile_binary = retrieved_profile.profile_binary
                        if devices_numbers != None:
                            present_profile.profile_devices = [DeviceProfileAssociation(device_number, present_profile.id) for device_number in devices_numbers]
                        else:
                            present_profile.profile_devices = []

            for profile_numbers_pair in new_profiles_with_associated_devices_numbers:
                profile, devices_numbers = profile_numbers_pair
                if devices_numbers != None and devices_numbers != {}:
                    profile.profile_devices = [DeviceProfileAssociation(number, profile.id) for number in devices_numbers]
                sqlalchemy_session.add(profile)
            sqlalchemy_session.commit()

            new_devices_count = len(new_devices_numbers)


        sqlalchemy_session.close()
        return {'retrieved_devices_count' : retrieved_devices_count, 'new_devices_count' : new_devices_count, 'retrieved_profiles_count' : retrieved_profiles_count, 'new_profiles_count' : new_profiles_count}


    def get_new_old_profiles_ids_map(self, edited_profiles):
        #print 'get_new_old_profiles_ids_map: ', edited_profiles.items()
        #return { profile_item[0] : profile_item[1]['new_profile_id'] for profile_item in edited_profiles.items() if profile_item[1]['success'] == True}
        return edited_profiles

    #def get_old_profiles_ids(self. edited_profiles):
    #    return 

    def replace_edited_profiles(self, edited_profiles):
        result = False
        url_error_detected = False
        profiles_data = self.get_provisioning_profiles_data()

        if profiles_data == None:
            url_error_detected == True
        if not url_error_detected:
            all_profiles_list = profiles_data.get('provisioningProfiles')
            new_old_profiles_ids_map = self.get_new_old_profiles_ids_map(edited_profiles)
            old_profiles_ids = new_old_profiles_ids_map.keys()
            new_profiles_ids = new_old_profiles_ids_map.values()

            new_profiles_with_associated_devices_numbers = list()
           
            sqlalchemy_session = self.sqlalchemy_db.session()
            associations_delete_result = sqlalchemy_session.query(DeviceProfileAssociation).filter(DeviceProfileAssociation.profile_id.in_(old_profiles_ids)).delete(synchronize_session='fetch')
            old_profiles_delete_result = sqlalchemy_session.query(Profile).filter(Profile.id.in_(old_profiles_ids)).delete(synchronize_session='fetch')

            sqlalchemy_session.flush()

            new_profiles = [Profile(profile_description) for profile_description in all_profiles_list if profile_description.get('provisioningProfileId') in new_profiles_ids]

            for profile in new_profiles:
                profile_id = profile.id
                profile_devices_numbers = []
                profile_binary = self.download_provisioning_profile(self.profile_download_url % profile_id)
                if profile_binary != None:
                    profile.profile_binary = profile_binary
                    profile_devices_numbers = self.extract_devices_from_profile(profile_binary)
                    new_profiles_with_associated_devices_numbers.append((profile, profile_devices_numbers))
                else:
                    url_error_detected = True
                    break
            if not url_error_detected:
                for profile_numbers_pair in new_profiles_with_associated_devices_numbers:
                    profile, devices_numbers = profile_numbers_pair
                    if devices_numbers != None and devices_numbers != {}:
                        profile.profile_devices = [DeviceProfileAssociation(number, profile.id) for number in devices_numbers]
                    sqlalchemy_session.add(profile)
                    
                result = True     
                sqlalchemy_session.commit()
        return result

    def add_multiple_devices(self, devices_file_context):

        result_message = 'unknow result'
        response_html = ''

        dacbrowser = self.dacbrowser
        url = self.add_device_url
        
        response = dacbrowser.get(url)
        #if dacbrowser.get(url) != None:
        if response.no_url_errors():
            device_import_form_found = dacbrowser.select_form_by_name('deviceImport')
            if device_import_form_found == False:
                print 'Device import form not found. Cleaning cookies'
                dacbrowser.clean_credentials()
                print 'Done. Try login with current login and password again'
                device_import_form_found = dacbrowser.select_form_by_name('deviceImport')
            if device_import_form_found:

                form = dacbrowser.current_form
                form.method = 'POST'
    
                devices_list_file = NamedTemporaryFile(prefix='apple_devices_list_', suffix='.txt')
                devices_list_file.write(devices_file_context)
                devices_list_file.flush()
                devices_list_file_name = devices_list_file.name

                for control in form.controls:
                    if control.type == 'file':
                        control.add_file(open(devices_list_file_name), 'text/plain', devices_list_file_name)
                        control.disabled=False
                        break
                
                response = dacbrowser.submit_selected_form()
                devices_list_file.close()

                response_data = response.get_data()
                if response_data != None and response_data != '':
                    parser = etree.HTMLParser()
                    html_tree = etree.parse(StringIO(response_data), parser)
                    error_message_container = html_tree.xpath('//ul[@class="errorMessage"]')
                    if error_message_container != [] and len(error_message_container) == 1:
                        error_message = etree.tostring(error_message_container[0])
                        print error_message
                        response_html = error_message
                        result_message = 'devices description error'
                    else:
                        device_import_save_form_found = dacbrowser.select_form_by_id('deviceImportSave')
                        if device_import_save_form_found:
                            form = dacbrowser.current_form
                            form.method = 'POST'
                            
                            #response = dacbrowser.submit_selected_form() ##-- Don't delete this line! This is for reall add devices!

                            if response_data != None and response_data != '':
                                html_tree = etree.parse(StringIO(response_data), parser)

                                '''
                                # Lines below - for real add devices.

                                success_message_container = html_tree.xpath('//<success tag xpath')
                                if success_message_container != [] and len(success_message_container) == 1:
                                    success_message = etree.tostring(success_message_container[0])
                                    result_messages = 'devices added successfuly'
                                    response_html = success_message
                                    '''

                            result_messages = 'devices added successfuly'
            else:
                result_message = 'Add multiple devices form not found. Probably your permissions not enough to add multiple devices. Try change login or/and password'
        else:
            result_message = 'cannot open url'

        print 'add_multiple_devices, result_message: ', result_message, ' response_html: ', response_html

        return result_message, response_html

    def select_devices_in_edit_form(self, device_ids_control, device_ids_list):
        for item in device_ids_control.items:
            for device_id in device_ids_list:
                if item.name == device_id:
                    if item.selected == True:
                        item.selected = False
                    else:
                        item.selected = True
                    break

    def select_and_prepare_profile_edit_form(self, profile_id, device_ids_list):
        profile_edit_response = self.dacbrowser.post(self.profile_edit_url, {'type' : '', 'provisioningProfileId' : profile_id, 'clientToken' : 'undefined'})
        dacbrowser = self.dacbrowser
        profile_edit_form_found = dacbrowser.select_form_by_name('profileEdit')
        if profile_edit_form_found:
            dacbrowser.add_adssuv_field_to_form()
            dacbrowser.prepare_csrf_headers()
          
            form = dacbrowser.current_form
            device_ids_control = form.find_control(name='deviceIds')
            self.select_devices_in_edit_form(device_ids_control, device_ids_list)

            form.method = 'POST'

        return profile_edit_form_found

    def get_profiles_ids(self, profiles_name_list, profiles_data_list):
        profile_ids = list()
        for profile_data in profiles_data_list:
            for profile_name in profiles_name_list:
                if profile_name == profile_data['name']:
                    profile_ids.append(profile_data['provisioningProfileId'])
                    print 'Profile id: ', profile_data['provisioningProfileId']
                    break
        return profile_ids

    def edit_profile(self, profile_id, devices_ids_list):
        dacbrowser = self.dacbrowser
        edit_success = False
        profile_edit_form_found = self.select_and_prepare_profile_edit_form(profile_id, devices_ids_list)
        
        new_profile_id = None
        profile_name = None
        submit_profile_edit_form_response = None
        if profile_edit_form_found:
            submit_profile_edit_form_response = dacbrowser.submit_selected_form()
            http_error_occured = submit_profile_edit_form_response.is_http_errors()
            http_code = submit_profile_edit_form_response.get_http_code()
            #print 'edit_profile, http_code: ', http_code, ' http_code type: ', type(http_code)
            if submit_profile_edit_form_response.no_url_errors():
                if http_error_occured and http_code == self.required_csrf_http_code:
                    profile_edit_form_found = self.select_and_prepare_profile_edit_form(profile_id, devices_ids_list)
                    if profile_edit_form_found:
                        submit_profile_edit_form_response = dacbrowser.submit_selected_form()

                if not http_error_occured:

                    profile_regeneration_result = submit_profile_edit_form_response.read()
                    result_dictionary = json.loads(profile_regeneration_result)

                    creation_timestamp = result_dictionary.get('creationTimestamp')
                    result_code = result_dictionary.get('resultCode')
                    new_profile_id = None
                    profile_name = None
                    profile_description = result_dictionary.get('provisioningProfile')

                    if profile_description != None:
                        #print 'name in profile_description.keys(): ', 'name' in profile_description.keys()
                        new_profile_id = profile_description.get('provisioningProfileId')
                        profile_name = profile_description.get('name')
                    else:
                        pass
                        #print 'profile_description is None'

                    print 'new_profile_id: ', new_profile_id, 'profile_name: ', profile_name

                    if creation_timestamp != None and result_code == 0 and new_profile_id != None:
                        edit_success = True
                else:
                    pass
                    print 'submit_profile_edit_form_response: ', submit_profile_edit_form_response
            else:
                pass
                print 'url error occured'
        else:
            pass
            print 'Profile with id %s not found!' % profile_id

        return edit_success, new_profile_id, profile_name
                   
    def edit_profiles(self, devices_ids_list, profiles_ids_list):
        edit_profiles_result_data = dict()
        dacbrowser = self.dacbrowser
        response = dacbrowser.get(self.profiles_url)
        self.get_provisioning_profiles_data()
        if self.profiles_data != None and self.profiles_data != {}:
            for profile_id in profiles_ids_list:
                print 'edit_profiles, profile_id: ', profile_id
                profile_edit_success, new_profile_id, profile_name = self.edit_profile(profile_id, devices_ids_list)
                edit_profiles_result_data[profile_id] = {'success' : profile_edit_success, 'new_profile_id' : new_profile_id, 'profile_name' : profile_name}
        return edit_profiles_result_data