#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013, John Hampton <pacopablo@pacopablo.com>
# All rights reserved
#
#
# Author: John Hampton <pacopablo@pacopablo.com>
USAGE = """
IMAP to Gmail Migration Tool

Usage:
  imap_to_gmail genconfig  [--config=FILE]
  imap_to_gmail migrate [--config=FILE]

Options:
  -h help           Show this screen
  --version         Show program version
  --config=FILE     Path to configuration file

Use `imap_to_gmail genconfig` to create a base configuration for the
migration.  Edit the resulting config file (config.json) and then pass it to 
`imap_to_gmail migrate` to move mail from IMAP to Gmail
"""[1:-1]

# Yeah, lazy crappy debugging
DEBUG = True

# Standard Library Imports
import sys
import imaplib
import getpass
import json
import socket
import os
import re
import time
import traceback
from pprint import pprint
from itertools import izip_longest

# Third Party Imports
from gdata.apps.migration import service
from gdata.service import BadAuthentication
import gdata.apps.emailsettings.client as gsettings
from docopt import docopt
from progressbar import Bar, Counter, Percentage, ProgressBar, Timer

DEBUG=1

VERSION='0.1.0'

RESPONSE = 0
DATA = 1

MESSAGE_IDS_RE = re.compile(
            r'\d+\s+\(FLAGS\s+\((?P<flags>.*)\)\s+UID\s+(?P<uid>\d+)\)$')

DEFAULT_EXCLUDED_FOLDERS = [
    'junk',
    'trash',
]

INPUT = {
    'imap_user' : {
        'info': 'Please enter the username to use when logging into the IMAP server.',
        'prompt': 'IMAP username',
        'warning': """
We use the IMAP username to get a list of existing folders from which to
migrate email.  If a username is not provided, you will have to populate
the list of folders to migrate manually."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'imap_server' : {
        'info': """
Please enter the IP or hostname of the IMAP server from which email will be
migrated.  If no server is specified, a blank configuration file will be
created."""[1:],
        'prompt': 'IMAP server',
        'warning': """
The server IP or hostname is required to prepopulate the configuration file
used for migration.  If it is not provided, a blank configuration file will be
created.  You will have to fill in the configuration by hand."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'imap_password' : {
        'info': """
Please enter the password for the IMAP user.  The password is not stored in
the configuration file, but it is used to pull the configuration from the
IMAP server."""[1:],
        'prompt': 'IMAP password',
        'warning': """
Without the password for the IMAP user, the list of folders to migrate can't
be populated and will need to be done manually."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': True,
    }, 

    'imap_migrated_dest' : {
        'info': """
Please specify a folder on the IMAP server where emails that have been
migrated can be placed.  If the folder exists already, it will be excluded from
the migration.  Additionally, emails that can not be successfully migrated
will be places in a "Failures" subfolder of the folder specified."""[1:],
        'prompt': 'IMAP folder for migrated emails',
        'warning': """
Without the IMAP folder, in which to place migrated emails, the migration will
not proceed."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'config_path' : {
        'info': """
Enter the filename to which the configuration should be saved. If a filename
is not specified, the configuration will be saved to `config.json` in the
current directory.
"""[1:],
        'prompt': 'Configuration filename',
        'warning': '',
        'warning_condition': lambda x: False,
        'hide': False,
    },

    'folder_list' : {
        'info': """
Specify the list of folders to exclude from migration as a comma separated
list. Leading and trailing spaces between entries will be stripped.  By
default, the following folders will not be migrated if they exist:

{}

If you want to include all folders, specify "-" as the list.  An empty 
list will migrate all but the default excluded folders."""[1:],
        'prompt': 'Excluded fodler list (comma separated)',
        'warning': '',
        'warning_condition': lambda x: False,
        'hide': False,
    },

    'gmail_email' : {
        'info': 'Enter the email address of the destination Gmail account',
        'prompt': 'Gmail email address',
        'warning': """
If an email address is not specified, it will have to be manually entered into
the configuration file before email can be migrated."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'gmail_admin' : {
        'info': """
Enter the email address of a Gmail domain administrator that has access to the
migration API.  If the domain administrator has enabled access to the
migration API for users, then this can be the same as the Gmail email above."""[1:],
        'prompt': 'Gmail administrator',
        'warning': """
Migration can not continue without specifying an account with access to the
migration API."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'gmail_password' : {
        'info': 'Enter the password for the Gmail admin account',
        'prompt': 'Gmail admin account password',
        'warning': 'Migration can not continue without the password',
        'warning_condition': lambda x: x == '',
        'hide': True,
    },

    'imap_migrate_user' : {
        'info': """
A username was not found in the config file.  Please specify the user name to
use when logging into the IMAP server."""[1:],
        'prompt': 'IMAP username',
        'warning': """
Email migration can not continue without a user from which the email should
be migrated."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'imap_migrate_server' : {
        'info': """
An IMAP server was not specified in the config file.  Please enter the
hostname or IP address of the IMAP server."""[1:],
        'prompt': 'IMAP server',
        'warning': """
Email migration can not continue without a server from which the email should
be migrated."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': False,
    },

    'imap_migrate_password' : {
        'info': """
Please enter the password for the IMAP user."""[1:],
        'prompt': 'IMAP password',
        'warning': """
A password is required to continue."""[1:],
        'warning_condition': lambda x: x == '',
        'hide': True,
    },
}


class NamespaceError(StandardError) : pass
class LoginError(StandardError)     : pass
class DieInAFireError(StandardError):
    """ Error from which the only option it to abort the program

    There are some errors such that the only real option is to simply quit the
    program and the user may restart the program to try again.  Some examples
    of this are a user mis-typing the IMAP server name twice, failure to
    authenticate to the TMAP server, etc.

    """
    pass


def getTrace():
    """ retrieve and format an exception into a nice message
    """
    msg = traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1],
        sys.exc_info()[2])
    msg = ''.join(msg)
    msg = msg.split('\012')
    msg = '\n'.join(msg)
    msg += '\n'
    return msg


def chunker(l, n):
    """ Takes a list and returns said list split into chunks of n elements
    """

    return [l[i:i+n] for i in range(0, len(l), n)]


def get_user_input(prompt_options):
    """Get information from the user via on-screen prompts

    A dicitonary of prompt info is required.  It should contain the following
    keys:

    info: Text provided befor the prompt to explain the information desired
          from the user

    prompt: short prompt for input. The string ' ==> ' will be appended to the
            prompt specified before displying on screen

    warning: a message shown when the user does not provide the information
             requested.  The user will be reprompted for the information

    warning_condition: a callable taking the input from the user and returning
                       whether or not the input given is satisfactory

    hide: whether or not to echo the user input.  Set to True to protect
          password and other sensitive information input

    """

    def get_input(hide):
        if hide:
            user_input = getpass.getpass(prompt_options['prompt'] + ' ==> ')
        else:
            user_input = raw_input(prompt_options['prompt'] + ' ==> ')
        return user_input
        

    print('\n\n' + prompt_options['info'] + '\n')
    input = get_input(prompt_options['hide'])
    if prompt_options['warning_condition'](input):
        print('\n' + prompt_options['warning'] + '\n')
        input = get_input(prompt_options['hide'])

    return input


def parse_namespace(nsinfo, separator):
    """ Extracts the namespace from the IMAP namspace tuple.

    For example, if the IMAP namespace was:

        ("user", ".")

    then the namespace of 'user' would be returned.
    """

    try:
        ns = nsinfo.strip('()')
        ns = ns.replace('" "', '".|."')
        ns = ns.split('.|.')[0]
        namespace = ns.strip('"%s' % separator)
    except (ValueError, IndexError):
        raise NamespaceError
    return namespace

    
def parse_separator(nsinfo):
    """ Extracts the separator from the IMAP namspace tuple.

    For example, if the IMAP namespace was:

        ("user", ".")

    then the separator of '.' would be returned.
    
    """
    try:
        ns = nsinfo.strip('()')
        ns = ns.replace('" "', '".|."')
        ns = ns.split('.|.')[1]
        separator = ns.strip('"')
    except (ValueError, IndexError):
        raise NamespaceError
    return separator


def split_ns_category(nses):
    """ Split a namespace category into individual namespace strings

    Given a namespace category string as such:
        
        (("#shared/" "/") ("#public/" "/")("#ftp/" "/"))
        
    return a list of namespaces, al la:
        
        ['("#shared/" "/")', '("#public/" "/")', '("#ftp/" "/")']
        
    Bug: I'm not sure if it's actually allowed in the RFC, but this code 
    won't handle a namespace that looks liskte the following:
    (("" "/")("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2")("FLAG3" "FLAG4")))
    If someone wants to let me know, then I'll look into fixing it. So far
    I have not run into an implementation where this is a problem.
    """
    cat = nses[1:-1]
    cat = cat.replace(')(', ')|(')
    catlist = cat.split('|')
    return catlist


def split_namespaces(nsstr):
    """ Split a string of IMAP namespace tuples into a list of python strings.
    
    The NAMESPACE command returns a string with 3 namespace sections:
    personal, other user's, and shared.  Each section may consist of 'NIL',
    or a list of namespaces.  For all the gory details, read RFC 2342
    http://www.faqs.org/rfcs/rfc2342.html
        
    Some exapmles of the different strings are:
        (("" "/")) NIL NIL
        NIL NIL (("" "."))
        (("" "/")) NIL (("Public Folders/" "/"))
        (("" "/")) (("~" "/")) (("#shared/" "/") ("#public/" "/")("#ftp/" "/"))
        (("" "/")("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2"))) NIL NIL
        
    split_namespaces creates a list of each individual namespace.  For 
    example, using the 4th line above, we would get:
        ['("" "/")', '("~" "/")', '("#shared/" "/")', '("#public/" "/")',
         '("#ftp/" "/")']
    """
    # OK definitely need to refresh my memory of namspaces and what I was
    # thinking.  I think function definitely needs some tests.
    
    # this is goign to get hairy
    # rip off first namespace
    nslist = []
    s = nsstr
    # Attack the personal namespace
    if s.find('NIL') == 0:
        s = s[4:]
    elif s.find('((') == 0:
        nsend = s.find('))') + 2
        personalns = s[:nsend]
        nslist.extend(split_ns_category(personalns))
        s = s[nsend + 1:]
        pass

    # Now chop off the head of the other user's namespace
    if s.find('NIL') == 0:
        s = s[4:]
    elif s.find('((') == 0:
        nsend = s.find('))') + 2
        userns = s[:nsend]
        nslist.extend(split_ns_category(userns))
        s = s[nsend + 1:]
        pass
    
    # Finally, kill off the shared namespace
    if s.find('NIL') < 0:
        sharedns = s
        nslist.extend(split_ns_category(sharedns))
        pass
    
    return nslist


def get_ns_info(cnx):
    """ Query the IMAP server for namespace info.

    Parses the response from the server and return a list of tuples. Each
    tuple consists of the name of the namespace and the separator for the
    namespace: ie: ('user', '.')

    """
    nslist = []
    result = cnx.namespace()
    if result[0] != 'OK':
        raise NamespaceError
    ns = split_namespaces(result[1][0])
    for n in ns:
        separator = parse_separator(n)
        namespace = parse_namespace(n, separator)
        yield (namespace, separator)
    

def imap_login(server_prompt, user_prompt, password_prompt, options=None):
    """ Returns a connection to the IMAP server and config information

    Expects three prompts to pass to the `get_user_info` method.

    Prompts the user for the IMAP server, username, and password information
    and then connects to and logs into the IMAP server returning the
    connection for further use.

    At this time it only connects via SSL.  I'll accept patches to allow for
    insecure IMAP connections.

    The server name, username and password are returned as a dicitonary
    containing: 'imap_user', 'imap_password', and 'imap_server' keys

    """
   
    # Note: I don't really like this method of handling errors.  It makes the
    # method uncomfortably long and repetitious.  Perhaps this is just how
    # it's to be done.  Instead of re-trying input once on a failed attempt,
    # the try/except block could be put into a loop.  I don't know that I
    # want to constantly ask the user to try again.  I'm open to other options
    # for handling errors.
    if options is None:
        options = {}
    imap_server = options.get('imap_server', None)
    options['imap_server'] = imap_server if imap_server else \
                                                 get_user_input(server_prompt)
    try:
        cnx = imaplib.IMAP4_SSL(options['imap_server'])
    except socket.gaierror:
        print("Unable to connect to the server specified.  Please verify that")
        print("the server name was typed correctly.\n")
        options['imap_server'] = get_user_input(server_prompt)
        try:
            cnx = imaplib.IMAP4_SSL(options['imap_server'])
        except socket.gaierror, e:
            message = e.strerror
            if e.errno == 8:
                message = "Unable to resolve given hostname"
            raise DieInAFireError(message)
        except socket.error, e:
            raise DieInAFireError(e.strerror)

    imap_user = options.get('imap_user', None)
    options['imap_user'] = imap_user if imap_user else \
                                                    get_user_input(user_prompt)
    options['imap_password'] = get_user_input(password_prompt)
    try:
        cnx.login(options['imap_user'], options['imap_password'])
    except imaplib.IMAP4.error:
        print("Unable to log into server.  Please verify that the username ")
        print("and password are correct.\n")
        options['imap_user'] = get_user_input(user_prompt)
        options['imap_password'] = get_user_input(password_prompt)
        try:
            cnx.login(options['imap_user'], options['imap_password'])
        except imaplib.IMAP4.error, e:
            raise DieInAFireError(e.message)
    return cnx, options


def get_inbox_ns(cnx):
    """ Return the namespace info for INBOX if it exists

    """

    namespace = separator = ''
    # TODO: Is there a better way to do this?  get_ns_info() is an iterable
    # returning tuples containing the namespace and the separator.  I only
    # care to match the namespace, but once matched, I do need the
    # corresponding separator
    for ns in get_ns_info(cnx):
        if 'INBOX' in ns:
            namespace, separator = ns
            break
        continue
    return (namespace, separator)


def get_imap_folder_list(cnx):
    """ Returns an iterable of the list of folders on the IMAP server.

    This only returns folders that are in the INBOX.

    """
    namespace, _ = get_inbox_ns(cnx)
    for folder_str in cnx.list(namespace)[DATA]:
        i = folder_str.find(namespace)
        folder = folder_str[i:]
        folder = folder[:folder.find('"')]
        yield folder


def get_excluded_folder_list(cnx):
    """ Get the list of excluded folders from the user.

    Displays a list of folders on the IMAP server and gets a list of folders
    to exclude from the migration.  Validates excluded folders against the list
    of actual folders

    """
    global INPUT, DEFAULT_EXCLUDED_FOLDERS

    separator = get_inbox_ns(cnx)[1]
    folders = []
    excluded_folders = []
    for f in get_imap_folder_list(cnx):
        parts = f.split(separator)
        if parts[-1].lower() in DEFAULT_EXCLUDED_FOLDERS:
            excluded_folders.append(f)
        else:
            folders.append(f)
        continue

    indent = " " * 4
    print("The following folders were found in the IMAP mailbox:\n\n")
    print(indent + ('\n' + indent).join(folders))
    INPUT['folder_list']['info']= INPUT['folder_list']['info'].format(
                                            '\n'.join(excluded_folders))
    user_folders = get_user_input(INPUT['folder_list'])

    if user_folders == '-':
        excluded_folders.extend(folders)
    elif user_folders:
        # TODO: Clean this up.  There has to be a better algorithm
        # The goal is to add the list of folders specified by the user that
        # match actual folders in the IMAP server to the excluded list.  The
        # match should be case-insensitive, and the string added to the
        # excluded list should be that pulled from the IMAP server
        orphans = []
        for f in user_folders.split(','):
            for imap_folder in folders:
                if f.lower() == imap_folder.lower():
                    excluded_folders.append(imap_folder)
                    break
            else:
                orphans.append(f)
            continue

        if orphans:
            print("The following folders were not found in the list of existing")
            print("folders.  Please add them manually if desired")
            print("\n" + indent + ('\n' + indent).join(orphans))

    return excluded_folders


def get_migrated_dest_folder(cnx):
    """ Get the IMAP folder where migrated emails should be stored

    """
    global INPUT

    dest = get_user_input(INPUT['imap_migrated_dest'])
    if not dest:
        raise DieInAFireError('An IMAP where migrated emails are to be stored'
                              ' is required.')
    namespace, separator = get_inbox_ns(cnx)
    if not dest.lower().startswith(namespace.lower() + separator):
        dest = namespace + separator + dest
    return dest


def get_config_path():
    """ Return the full path for the config file

    Prompts the user for the path where the config should be stored.  If no
    path is given, it defaults to `config.json` in the working directory

    """

    path = get_user_input(INPUT['config_path'])
    if not path:
        path = 'config.json'
    abspath = os.path.abspath(path)
    if os.path.isfile(abspath):
        choice = raw_input("The file {} already exists.  Are you sure you want "
                           "to overwrite it? [y/n] ==> ".format(path))
        if not choice.lower() in ['y', 'yes']:
            raise DieInAFireError('Will not overwrite the config file.')
        try:
            config = open(abspath, 'wb')
            config.close()
        except IOError, e:
            raise DieInAFireError('Unable to save config file:\n' + e.strerror)
    elif os.path.exists(abspath):
        raise DieInAFireError('The path specified is not a file.  Please '
                              'chose another path')
    return abspath


def load_config(path):
    """ Load the config at the give path

    If the path is empty or None, look for the config in the working directory
    in the file named `config.json`

    """
    if not path:
        path = 'config.json'
    return json.load(open(path, 'rb'))


def google_login(options):
    """ Log into the Google migration and email settings APIs

    """

    gmail_email = options.get('gmail_email', None)
    options['gmail_email'] = gmail_email if gmail_email else \
                                         get_user_input(INPUT['gmail_email'])
    try:
        user, domain = options['gmail_email'].split('@')
    except ValueError:
        raise DieInAFireError('Unable to extract the username and domain '
                              'from\nthe gmail email address provided: ' +
                              options['gmail_email'])
    options['gmail_admin'] = get_user_input(INPUT['gmail_admin'])
    options['gmail_password'] = get_user_input(INPUT['gmail_password'])
    srv = service.MigrationService(email=options['gmail_admin'],
                                   password=options['gmail_password'],
                                   domain=domain,
                                   source='imap-to-gmail')
    settings = gsettings.EmailSettingsClient(domain=domain)

    try:
        srv.ProgrammaticLogin()
        settings.ClientLogin(email=options['gmail_admin'],
                             password=options['gmail_password'],
                             source='imap-to-gmail')

    except BadAuthentication, e:
        raise DieInAFireError('Unable to authenticate to Google: ' + e.message)
    return (srv, settings)


def prepare_imap_folders(cnx, folder):
    """ Prepare the "migrated" folder on the IMAP server """

    #cnx.debug = 5
    # Python IMAP bug: when passed unicode strings the select method (possibly
    # others?) doesn't quote the string.  This means that things go kablooey
    # if your folders have spaces in them.
    folder = str(folder)
    if 'NO' == cnx.select(folder)[RESPONSE]:
        resp = cnx.create(folder)
        if 'NO' == resp[RESPONSE]:
            raise DieInAFireError('Unable to prepare IMAP folder {0}.\n'
                                  'Reason:\n{1}\n'.format(folder,
                                                          resp[DATA][0]))
    #cnx.debug = 0


def get_message_ids(cnx):
    """ Returns a generator of (UID, FLAGS) tuples for the currently selected
    mailbox

    """
    global MESSAGE_IDS_RE

    resp = cnx.fetch('1:*', '(UID FLAGS)')
    if 'OK' == resp[RESPONSE]:
        for data in resp[DATA]:
            # Example: '1 (FLAGS (\\Seen) UID 1)'
            # Even though it may now have two problems, this is screams regex
            parts = MESSAGE_IDS_RE.match(data).groupdict()
            flags = parts['flags'].split(' ')
            yield (parts['uid'], flags)
    else:
        raise StopIteration


def fetch_message_chunk(cnx, start_uid, end_uid):
    """ Fetch messages start_uid:end_uid from the IMAP server

    Returns a dictionary of {uid: RFC822 message}
    """
    resp = cnx.uid('fetch', '{}:{}'.format(start_uid, end_uid), 'RFC822')
    if 'OK' == resp[RESPONSE]:

    else:
        raise DieInAFireError('Unable to fetch messages {0}:{1} from IMAP '
                              'server.\n'
                              'Reason:\n{2}\n'.format(start_uid, end_uid,
                                                      resp[DATA][0]))


def folder_is_excluded(excluded_folders, folder):
    """ Return whether or not the folder is excluded.

    A folder is excluded if it is in the `excluded_folders` list or is a child
    of a folder in the `excluded_folders` list

    """

    if folder in excluded_folders:
        excluded = True
    else:
        # is there a more pythonic / easier way to do this?
        excluded = False
        for f in excluded_folders:
            # We don't want to match children of INBOX since all folders are
            # children of INBOX
            if folder.startswith(f) and 'INBOX' != f:
                excluded = True
                break

    return excluded


def parse_flags(folder, flags, sep):
    """ Parse IMAP flags to Gmail properties and tags

    Property mappings:

    IS_DRAFT:   email located in INBOX.Drafts
    IS_INBOX:   email located in INBOX
    IS_SENT:    email located in INBOX.Sent
    IS_UNREAD:  email with out the \Seen IMAP flag
    IS_STARRED: email with the \Flagged IMAP flag
    IS_TRASH:   email in INBOX.Trash, if it was selected for migration

    Flags:

    $Label1:  Important
    $Label2:  Work
    $Label3:  Personal
    $Label4:  To Do
    $Label5:  Later

    Any flag without a leading \ or $ is translated directly to a label

    """

    PRE_DEFINED_LABELS = {
        '$Label1': 'Important',
        '$Label2': 'Work',
        '$Label3': 'Personal',
        '$Label4': 'To Do',
        '$Label5': 'Later',
    }


    properties = []
    labels = []
    seen = False
    if folder.lower() == 'inbox':
        properties.append('IS_INBOX')
    elif folder.lower().startswith('inbox' + sep + 'drafts'):
        properties.append('IS_DRAFT')
    elif folder.lower().startswith('inbox' + sep + 'sent'):
        properties.append('IS_SENT')
    elif folder.lower().startswith('inbox' + sep + 'trash'):
        properties.append('IS_TRASH')

    for f in flags:
        if f.startswith('$'):
            glabel = PRE_DEFINED_LABELS.get(f, None)
            if glabel:
                labels.append(glabel)
        elif f.startswith('\\'):
            if '\\Flagged' == f:
                properties.append('IS_STARRED')
        else:
            if f:
                labels.append(f)
        continue

    if not seen:
        properties.append('IS_UNREAD')
        if folder.lower() == 'inbox':
            labels.append('IMAP Migration')

    return (properties, labels)


def create_folder(folder, sep, gset_cnx, user):
    """ Create the IMAP folder structure using Gmail labels

    """
    FORBIDDEN_LABELS = ['archive', 'unread', 'drafts', 'sent', 'char',
                        'muted', 'spam', 'trash', 'popped', 'contactcsv']
    folder_base = ''
    for i, p in enumerate(folder.split(sep)[1:]):
        if i == 0 and p.lower() in FORBIDDEN_LABELS:
            p = '_' + p
        folder_base += p
        gset_cnx.CreateLabel(username=user, name=folder_base)
        # noinspection PyUnusedLocal
        folder_base += '/'
        continue

    return [folder_base.rstrip('/')]


def migrate_email(imap_cnx, gsrv_cnx, gset_cnx, options):
    """ Move email from IMAP to Gmail """

    MESSAGE_SET_SIZE = 50
    _, separator = get_inbox_ns(imap_cnx)
    widgets = ['Processed: ', Counter(), ' emails (', Timer(), ')  ',
               Percentage(), Bar()]
    pbar = ProgressBar(widgets=widgets)
    options['folder_list'].append(options['imap_migrated_dest'])
    for folder in get_imap_folder_list(imap_cnx):
        if not folder_is_excluded(options['folder_list'], folder):
            print("Migrating folder: {}".format(folder))
            migrated_folder = separator.join([options['imap_migrated_dest']] +
                                              folder.split(separator)[1:])
            prepare_imap_folders(imap_cnx, migrated_folder)
            # See Python IMAP bug referenced in `prepare_imap_folders`
            resp = imap_cnx.select(str(folder))
            if 'OK' == resp[RESPONSE]:
                num_emails = int(resp[DATA][0])
                if 0 == num_emails:
                    print('{} empty. Skipping ...'.format(folder))
                else:
                    pbar.maxval = num_emails
#                    pbar.start()
                    i = 0
                    for uid_chunk in chunker([ids for ids in get_message_ids(
                                                imap_cnx)], MESSAGE_SET_SIZE):
                        messages = fetch_message_chunk(imap_cnx,
                                                       uid_chunk[1][0],
                                                       uid_chunk[-1][0])
                        print('-------- Chunk Start -------')
                        for uid, flags in uid_chunk:
                            print('UID: {} - Flags: {}'.format(uid, str(flags)))
                            continue
                        print('-------- Chunk End ---------')
                        continue

                    comment = """
                    for uid, flags, msg in get_messages(imap_cnx, MESSAGE_SET_SIZE):
                        i += 1
                        mail = imap_cnx.uid('fetch', uid, 'RFC822')
                        user, domain = options['gmail_email'].split('@')
                        properties, labels = parse_flags(folder, flags, separator)
                        labels += create_folder(folder, separator, gset_cnx,
                                                user)
                        gsrv_cnx.ImportMail(user_name=user,
                                        mail_message=mail[DATA][0][1],
                                        mail_item_properties=properties,
                                        mail_labels=labels)
                        if not '\\Seen' in flags:
                            imap_cnx.uid('store', uid, '-FLAGS', '(\\Seen)')
                        pbar.update(i)
                    pbar.finish()
"""
            else:
                print("Error converting folder: {}".format(resp[DATA][0]))

    pass


def main(args):
    """ Migrate email from IMAP to Gmail

    """
    global USAGE, INPUT

    rc = 0
    options = {}
    pprint(args)
    try:
        if args['genconfig']:
            # Ask for name of IMAP folder to place migrated emails
            # Print list of folders
            # Ask for list of excluded folders. [Sent, Trash]
            # Ask for Gmail email address
            # Write out config file
            cnx, imap_info = imap_login(INPUT['imap_server'],
                                        INPUT['imap_user'],
                                        INPUT['imap_password'])
            options.update(imap_info)
            options['folder_list'] = get_excluded_folder_list(cnx)
            options['imap_migrated_dest'] = get_migrated_dest_folder(cnx)
            options['gmail_email'] = get_user_input(INPUT['gmail_email'])
            options['config_path'] = get_config_path()
            del options['imap_password']
            open(options['config_path'], 'wb').write(
                        json.dumps(options,indent=4, separators=(',', ': ')))
        elif args['migrate']:
            print('migrate')
            # Load config file
            print('loading_config')
            options = load_config(args['--config'])
            print('finish loading_config')
            if not options:
                print('options is blank')
            pprint(options)
            # Log into IMAP
            cnx, _ = imap_login(INPUT['imap_migrate_server'],
                            INPUT['imap_migrate_user'],
                            INPUT['imap_migrate_password'],
                            options)
            cnx.debug = 5

            # Log into Google Migration and Email Settings APIs
            gsrv, gset = google_login(options)
            # prep migration "done" IMAP folder
            prepare_imap_folders(cnx, options['imap_migrated_dest'])
            _, separator = get_inbox_ns(cnx)
            prepare_imap_folders(cnx, options['imap_migrated_dest'] +
                                      separator + 'Failures')
            # Perform migration work here
            migrate_email(cnx, gsrv, gset, options)
            # Migrate email to Gmail
            #   Move read email to INBOX and/or corresponding folder
            #   Move unread email in INBOX to 'Migrated/Unread'
            #   Move unread email in folder to corresponding folder
            # Move mail to "done" IMAP folder
            # If a message fails, move to "done/failed" IMAP folder
        else:
            print(USAGE)
            rc = 1
    except DieInAFireError, e:
        print('Unable to continue due to non-recoverable error:\n\n')
        print(e.args[0])
        if DEBUG:
            print(getTrace())
        rc = 1
    except Exception, e:
        print(getTrace())

    return rc


if __name__ == '__main__':
    rc = 0
    stderr_file = open('imap_to_gmail.log', 'a+b')
    sys.stderr = stderr_file
    try:
        args = docopt(USAGE, help=True,
                      version='IMAP to Gmail Migration Tool {0}'.format(VERSION))
        rc = main(args)
    finally:
        stderr_file.close()
    sys.exit(rc)
