#! /usr/bin/env python

from encodings import hex_codec
import gdata.photos.service
import gdata.media
import gdata.geo
import os, stat
import socket
import getpass
import smtplib
import json
import sys
import argparse
import urllib
import string
from datetime import datetime
from backasa.album import Album
from backasa.photo import Photo


# Command argument setup
parser = argparse.ArgumentParser(description="Tool to make local downstream backups of your Picasa Web Albums", add_help=False)
options = parser.add_argument_group('Options')

options.add_argument('-e', '--email',
                    help="Specify the Picasa user's email to backup.",
                    required=False)

options.add_argument('-p', '--password',
                    help="Specify the Picasa password for the user you are backing up.",
                    required=False)


options.add_argument('-t', '--target',
                    help="Specify the backasa target directory of the backup.  Defaults to the current working directory.",
                    required=False,
                    default=os.getcwd())

options.add_argument('-n', '--notify',
                     help="flag to notify user of completion via email with full log included",
                     action="store_true",
                     default=False)

options.add_argument('-h', '--help', action="help")

options.add_argument('action',
                    help="setup - action to prep directory with stored credentials | backup - run an update for an existing target or create a new one | cleanup - remove all '.old' files and files deleted by picasa.",
                    choices=('setup', 'backup', 'cleanup'))

args = None
albums = None
gdc = gdata.photos.service.PhotosService()

def format_filename(s):
    valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
    filename = ''.join(c for c in s if c in valid_chars)
    filename = filename.replace(' ','_')
    return filename

def get_album_dir(album):
    return os.path.join(os.getcwd(), format_filename(album.name) + "-" + album.album_id)

def picasa_prep():
    if os.path.isfile("backasa.config"):
        config = json.load(open("backasa.config"))
    else:
        config = None

    if args["email"]:
        gdc.email = args["email"]
    else:
        if config is None:
            print("No config file found, must privide an email or run 'setup' action")
            exit()
        else:
            if "email" in config:
                gdc.email = config["email"]
            else:
                print("No email found in config file, must provide an email.  Run 'setup' to generate a config file")
                exit()

    if args["password"]:
        gdc.password = args["password"]
    else:
        if config is None:
            print("No config file found, must privide an password or run 'setup' action")
            exit()
        else:
            if "password" in config:
                gdc.password = config["password"]
            else:
                print("No password found in config file, must provide an password.  Run 'setup' to generate a config file")
                exit()

    gdc.source = "Backasa"

    try:
        gdc.ProgrammaticLogin()
    except Exception, e:
        print("There was a problem with your Credentials - " + str(e))
        exit()

    try:
        gdc.GetUserFeed()
    except:
        print("Make sure given user has picasa access.")
        exit()


def setup():

    config = {}
    config["email"] = args["email"] if args["email"] else raw_input("Picasa Email: ")
    config["password"] = args["password"] if args["password"] else getpass.getpass("Picasa Password: ")
    f = open('backasa.config', 'w')
    json.dump(config, f)
    f.close()
    os.chmod('backasa.config', stat.S_IRUSR | stat.S_IWUSR)
    print("Config file created!")


def backup():
    sys.stdout.write("Creating / Updating Backasa Database......")

    # Create DB if doesn't exist
    Photo.create_db()
    Album.create_db()

    try:
        albums = gdc.GetUserFeed()
    except:
        print("Google Puked on the albums")
        exit()

    for picasa_album in albums.entry:
        album = Album.find(picasa_album.gphoto_id.text)
        if album is None:
            album = Album.fromPicasa(picasa_album)
            album.save()
            album_dir = get_album_dir(album)
            if not os.path.isdir(album_dir):
                os.mkdir(album_dir)

        album.name = picasa_album.title.text
        album.save()
        album_dir = get_album_dir(album)
        f = open(os.path.join(album_dir, "album.json"), "w")
        json.dump({"album_id": album.album_id, "album_name": album.name}, f)
        f.close()

        try:
            photos = gdc.GetFeed('/data/feed/api/user/default/albumid/%s?kind=photo&imgmax=d' % picasa_album.gphoto_id.text)
        except:
            print("Google Puked on the photos")
            exit()

        for picasa_photo in photos.entry:
            photo = Photo.find(picasa_photo.gphoto_id.text)
            if photo is None:
                photo = Photo.fromPicasa(picasa_photo)
                photo.save()
            else:
                if int(picasa_photo.version.text) > photo.current_version:
                    photo.downloaded = False
                    old_version = photo.current_version
                    photo.current_version = int(picasa_photo.version.text)
                    photo.updated_at = datetime.now()
                    photo.save()
                    photo_path = os.path.join(get_album_dir(album), photo.file_name)
                    if os.path.exists(photo_path):
                        os.rename(photo_path, photo_path + ".v" + str(old_version) + ".old")
    print("Done!\n")

    def fileProgress(count, blockSize, totalSize):
        percent = int(count * blockSize * 100 / totalSize)
        percent = 100 if percent > 100 else percent
        sys.stdout.write("\x08" * 6)
        sys.stdout.write("[%3d%%]" % percent, (percent == 100))

    # now go about downloading the files
    bAlbums = Album.findAll()
    for album in bAlbums:
        album_dir = get_album_dir(album)
        title_str = "Updating %s into %s" % (album.name, album_dir)
        print("_" + ("_" * len(title_str)))
        print("|" + title_str)
        print("|")
        photo_downloaded = False
        for i, photo in enumerate(album.undownloaded_photos):
            sys.stdout.write("|--> %d of %d file(s) [  0%%]" % (i + 1, len(album.undownloaded_photos)))
            urllib.urlretrieve(photo.url, os.path.join(album_dir, photo.file_name), reporthook=fileProgress)
            sys.stdout.write("\x08" * 52)
            if os.path.isfile(os.path.join(album_dir, photo.file_name)):
                photo.downloaded = True
                photo.save()
                photo_downloaded = True

        if not photo_downloaded:
            print("|--> No Files to Upload.")
        print("")
    print(str(len(bAlbums)) + " Album(s) Updated.")


def cleanup():
    if not os.path.isfile(os.path.join(os.getcwd(), "backasa.sqlite")):
        print("Error: Target directory exists but does not appear to be a backasa backup.  No database was found")
        exit()

    sys.stdout.write("Marking files that No longer exist in Picasa...")
    # Create DB if doesn't exist

    Photo.create_db()
    Album.create_db()

    photo_ids = []
    albums = gdc.GetUserFeed()
    for picasa_album in albums.entry:
        photos = gdc.GetFeed('/data/feed/api/user/default/albumid/%s?kind=photo&imgmax=d' % picasa_album.gphoto_id.text)
        photo_ids.extend([picasa_photo.gphoto_id.text for picasa_photo in photos.entry])

    photo_ids = set(photo_ids)
    local_ids = set(Photo.findAllIds())

    ids_to_delete = local_ids.difference(photo_ids)
    print("Done!\n")
    print("Removing all '.old' files from backup...")
    print("----------------------------------------")
    for _id in ids_to_delete:
        photo = Photo.find(_id)
        photo_path = os.path.join(get_album_dir(photo.album), photo.file_name)
        os.rename(photo_path, photo_path + ".old")
        photo.delete()

    delete_count = 0
    from glob import glob
    for i in os.listdir(os.getcwd()):
        if os.path.isdir(i):
            for f in glob(os.path.join(i, "*.old")):
                print("Deleting -> %s" % f)
                os.remove(f)
                delete_count += 1
    print("\n %s file(s) removed from backup" % str(delete_count))


# Utility methods / classes

def confirm(prompt=None, resp=False):
    if prompt is None:
        prompt - "Confirm?"

    if resp:
        prompt = '%s [%s]|%s: ' % (prompt, 'y', 'n')
    else:
        prompt = '%s [%s]|%s: ' % (prompt, 'n', 'y')

    while True:
        ans = raw_input(prompt)
        if not ans:
            return resp
        if ans not in ['y', 'Y', 'n', 'N']:
            print 'please enter y or n.'
            continue
        if ans == 'y' or ans == 'Y':
            return True
        if ans == 'n' or ans == 'N':
            return False


def notify_via_email():
    server = smtplib.SMTP('smtp.gmail.com:587')
    server.starttls()
    server.login(gdc.email, gdc.password)
    message = """\
From: %s
To: %s
Subject: %s

%s
""" % ("Bakasa@" + socket.gethostname(), gdc.email, "Backasa Backup Report", sys.stdout.getLog())
    server.sendmail(gdc.email, gdc.email, message)
    server.quit()


class Logger(object):
    __log = ""

    def __init__(self, filename="backasa.log"):
        self.terminal = sys.stdout
        self.log = open(filename, "a")

    def write(self, message, add_to_log=True):
        self.terminal.write(message)
        if add_to_log:
            self.log.write(message)
        self.terminal.flush()
        self.__log += message

    def getLog(self):
        return self.__log


if __name__ == "__main__":
    args = vars(parser.parse_args())

    if not os.path.isdir(args["target"]):
        if confirm("Target path is not a directory, attempt to create it? "):
            try:
                os.makedirs(args["target"])
            except:
                print("Error: Directory could not be created")
                exit()
        else:
            print("Error: Target directory does not exist")
            exit()

    os.chdir(args["target"])

    sys.stdout = Logger(os.path.join(os.getcwd(), "backasa.log"))

    print("\n==== Running Backasa %s - %s ====\n" % (args["action"].title(), datetime.now().strftime("%m/%d/%Y %I:%M%p")))

    if args["action"] == "cleanup":
        picasa_prep()
        cleanup()
        if args["notify"]:
            notify_via_email()
    elif args["action"] == "backup":
        picasa_prep()
        backup()
        if args["notify"]:
            notify_via_email()
    elif args["action"] == "setup":
        setup()

    print("\n==== Completed Backasa %s - %s ====\n" % (args["action"].title(), datetime.now().strftime("%m/%d/%Y %I:%M%p")))
