#!/usr/bin/python2
#
# Copyright 2008 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Contains the methods to import mail via Google Apps Email Migration API.

  MigrationService: Provides methods to import mail.
"""

__author__ = ('google-apps-apis@googlegroups.com',
              'pti@google.com (Prashant Tiwari)')


import base64
import threading
import time
from atom.service import deprecation
from gdata.apps import migration
from gdata.apps.migration import MailEntryProperties
import gdata.apps.service
import gdata.service


API_VER = '2.0'


class MigrationService(gdata.apps.service.AppsService):
  """Client for the EMAPI migration service.  Use either ImportMail to import
  one message at a time, or AddMailEntry and ImportMultipleMails to import a
  bunch of messages at a time.
  """

  def __init__(self, email=None, password=None, domain=None, source=None,
               server='apps-apis.google.com', additional_headers=None):
    gdata.apps.service.AppsService.__init__(
        self, email=email, password=password, domain=domain, source=source,
        server=server, additional_headers=additional_headers)
    self.mail_batch = migration.BatchMailEventFeed()
    self.mail_entries = []
    self.exceptions = 0

  def _BaseURL(self):
    return '/a/feeds/migration/%s/%s' % (API_VER, self.domain)

  def ImportMail(self, user_name, mail_message, mail_item_properties,
                 mail_labels):
    """Imports a single mail message.

    Args:
      user_name: The username to import messages to.
      mail_message: An RFC822 format email message.
      mail_item_properties: A list of Gmail properties to apply to the message.
      mail_labels: A list of labels to apply to the message.

    Returns:
      A MailEntry representing the successfully imported message.

    Raises:
      AppsForYourDomainException: An error occurred importing the message.
    """
    uri = '%s/%s/mail' % (self._BaseURL(), user_name)

    mail_entry = migration.MailEntry()
    mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode(
        mail_message)))
    mail_entry.rfc822_msg.encoding = 'base64'
    mail_entry.mail_item_property = map(
        lambda x: migration.MailItemProperty(value=x), mail_item_properties)
    mail_entry.label = map(lambda x: migration.Label(label_name=x),
                           mail_labels)

    try:
      return migration.MailEntryFromString(str(self.Post(mail_entry, uri)))
    except gdata.service.RequestError, e:
      # Store the number of failed imports when importing several at a time 
      self.exceptions += 1
      raise gdata.apps.service.AppsForYourDomainException(e.args[0])

  def AddBatchEntry(self, mail_message, mail_item_properties,
                    mail_labels):
    """Adds a message to the current batch that you later will submit.
    
    Deprecated, use AddMailEntry instead

    Args:
      mail_message: An RFC822 format email message.
      mail_item_properties: A list of Gmail properties to apply to the message.
      mail_labels: A list of labels to apply to the message.

    Returns:
      The length of the MailEntry representing the message.
    """
    deprecation("calling deprecated method AddBatchEntry")
    mail_entry = migration.BatchMailEntry()
    mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode(
        mail_message)))
    mail_entry.rfc822_msg.encoding = 'base64'
    mail_entry.mail_item_property = map(
        lambda x: migration.MailItemProperty(value=x), mail_item_properties)
    mail_entry.label = map(lambda x: migration.Label(label_name=x),
                           mail_labels)

    self.mail_batch.AddBatchEntry(mail_entry)

    return len(str(mail_entry))

  def SubmitBatch(self, user_name):
    """Sends all the mail items you have added to the batch to the server.
    
    Deprecated, use ImportMultipleMails instead

    Args:
      user_name: The username to import messages to.

    Returns:
      An HTTPResponse from the web service call.

    Raises:
      AppsForYourDomainException: An error occurred importing the batch.
    """
    deprecation("calling deprecated method SubmitBatch")
    uri = '%s/%s/mail/batch' % (self._BaseURL(), user_name)

    try:
      self.result = self.Post(self.mail_batch, uri,
                              converter=migration.BatchMailEventFeedFromString)
    except gdata.service.RequestError, e:
      raise gdata.apps.service.AppsForYourDomainException(e.args[0])

    self.mail_batch = migration.BatchMailEventFeed()

    return self.result

  def AddMailEntry(self, mail_message, mail_item_properties=None,
                   mail_labels=None, identifier=None):
    """Prepares a list of mail messages to import using ImportMultipleMails.
    
    Args:
      mail_message: An RFC822 format email message as a string.
      mail_item_properties: List of Gmail properties to apply to the
          message.
      mail_labels: List of Gmail labels to apply to the message.
      identifier: The optional file identifier string
    
    Returns:
      The number of email messages to be imported.
    """
    mail_entry_properties = MailEntryProperties(
        mail_message=mail_message,
        mail_item_properties=mail_item_properties,
        mail_labels=mail_labels,
        identifier=identifier)

    self.mail_entries.append(mail_entry_properties)
    return len(self.mail_entries)

  def ImportMultipleMails(self, user_name, threads_per_batch=20):
    """Launches separate threads to import every message added by AddMailEntry.
    
    Args:
      user_name: The user account name to import messages to.
      threads_per_batch: Number of messages to import at a time.
    
    Returns:
      The number of email messages that were successfully migrated.
    
    Raises:
      Exception: An error occurred while importing mails.
    """
    num_entries = len(self.mail_entries)

    if not num_entries:
      return 0

    threads = []
    for mail_entry_properties in self.mail_entries:
      t = threading.Thread(name=mail_entry_properties.identifier,
                           target=self.ImportMail,
                           args=(user_name, mail_entry_properties.mail_message,
                                 mail_entry_properties.mail_item_properties,
                                 mail_entry_properties.mail_labels))
      threads.append(t)
    try:
      # Determine the number of batches needed with threads_per_batch in each
      batches = num_entries / threads_per_batch + (
          0 if num_entries % threads_per_batch == 0 else 1)
      batch_min = 0
      # Start the threads, one batch at a time
      for batch in range(batches):
        batch_max = ((batch + 1) * threads_per_batch
                     if (batch + 1) * threads_per_batch < num_entries
                     else num_entries)
        for i in range(batch_min, batch_max):
          threads[i].start()
          time.sleep(1)

        for i in range(batch_min, batch_max):
          threads[i].join()

        batch_min = batch_max

      self.mail_entries = []
    except Exception, e:
      raise Exception(e.args[0])
    else:
      return num_entries - self.exceptions
