#!/usr/bin/python3

#-*- coding: utf-8 -*-

import sys
import getopt
import os
import subprocess
import time
import getpass
import requests
import git

class GithubAccount(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password
        if not self.is_valid():
            raise ValueError('Account is invalid. Username or password is incorrect.')
        
    def is_valid(self):
        import requests
        r = requests.get('https://api.github.com/notifications', 
            auth = (self.username, self.password))
        if r.status_code == 401: # Not Authorized
            return False
        return True

class GithubRepository(object):
    def __init__(self, name, url, owner, visibility):
        self.name = name
        self.url = url
        self.owner = owner
        self.visibility = visibility

"""
class ShellCommandException(Exception):
    def __init__(self, command):
        self.command = command
    
    def __str__(self):
        return repr(self.command)

def execute_shell_command(cmd):
    child = subprocess.Popen(cmd, shell=True)
    child.communicate()
    if child.returncode != 0:
        raise ShellCommandException(cmd)
"""

def make_and_chdir(dir_name):
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)
    os.chdir(dir_name)

class GithubBackup(object):
    def __init__(self, account):
        self.account = account
    
    def get_repo_list(self):
        base_url = 'https://api.github.com/user/repos'
        page = 1
        repo_list = []

        while True:
            url = base_url + '?page=' + str(page)
            r = requests.get(url,
                auth = (self.account.username, self.account.password))
            
            json_data_list = r.json()
            if len(json_data_list) == 0:
                break

            for json_data in json_data_list:
                repo_name = json_data['name']
                repo_url = json_data['html_url']
                owner = json_data['owner']['login']
                is_private = json_data['private']
                if is_private:
                    visibility = 'private'
                else:
                    visibility = 'public'
                repo = GithubRepository(repo_name, repo_url, owner, visibility)
                repo_list.append(repo)

            page += 1
            
        return repo_list


    def backup_repo(self, repo):
        old_directory = os.getcwd()
        try:
            make_and_chdir(repo.owner)
            make_and_chdir(repo.visibility)
            try:
                git.Git().clone(repo.url)
                git.Repo(repo.name).remotes.origin.fetch()
            except:
                if os.path.exists(repo.name):
                    import shutil
                    shutil.rmtree(repo.name, ignore_errors=False)
                raise
        finally:
            os.chdir(old_directory)

    def backup_failure_handler(self):
        backup_gap_time = time.time() - self.backup_stime
        print('\n[I] All progress time : {0:.4f} sec.'.format(backup_gap_time))
        print('[!] The progress was failed!')

    def backup(self, backup_name, compress):
        self.backup_dir = os.getcwd()
        self.backup_stime = time.time()

        print('[*] Get your repositories informations...')
        stime = time.time()
        repo_list = self.get_repo_list()
        gap_time = time.time() - stime
        print('[I] The number of repositories to be backupped : {0}'.format(len(repo_list)))
        print('[I] Progress time to get informations : {0:.4f} sec.'.format(gap_time))
        print('[*] Success to get repositories informations!\n')

        print('[*] Start to backup!\n')
        
        if os.path.exists(backup_name):
            print('[!] {0} already exists!'.format(backup_name))
            self.backup_failure_handler()
            return False

        make_and_chdir(backup_name)
        for repo in repo_list:
            print('[*] Backup-ing {0}/{1}...'.format(repo.owner, repo.name))
            stime = time.time()
            try:
                self.backup_repo(repo)
            except ShellCommandException as e:
                print('[!] A unknown error occurs when executing "{0}"'.format(e.cmd))
                self.backup_failure_handler()
                return False

            gap_time = time.time() - stime
            print('[I] Progress time to backup {0}/{1} : {2:.4f} sec.'.
                format(repo.owner, repo.name, gap_time))
            print('[*] Success to backup {0}/{1}!\n'.format(repo.owner, repo.name))
        
        print('[*] Finish to backup!\n')
        
        os.chdir(self.backup_dir)

        if compress:
            print('[*] Start to compress!')
            import tarfile
            with tarfile.open(backup_name + '.tar.gz', 'w:gz') as tar:
                for name in os.listdir(backup_name):
                    tar.add(backup_name + '/' + name)
            print('[*] Finish to compress!\n')

        backup_gap_time = time.time() - self.backup_stime
        print('[I] All progress time : {0:.4f} sec.'.format(backup_gap_time))
        print('[*] All progress was finished successfully!')
        
        return True

def usage():
    print("""Usage: {0} [Options]
Options:
    [-u] <username>                 : github username.
    [-p] <password>                 : github password.
    [-o | --output] <output name>   : a name of output directory or file.
    [-c]                            : compress outputs.
    [-h | --help | --usage]         : show usage.
""".format(sys.argv[0]))

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:],"u:p:o:ch",['output=','help','usage'])
    except getopt.GetoptError as err:
        print(str(err))
        usage()
        sys.exit(1)
    
    username = None
    password = None
    output = 'github-backup-data'
    compress = False

    for opt, arg in opts:
        if opt == '-u':
            username = arg
        elif opt == '-p':
            password = arg
        elif opt == '-o' or opt == '--output':
            output = arg
        elif opt == '-c':
            compress = True
        else:
            usage()
            sys.exit(1)

    if username is None:
        username = input('Input your github username : ')

    if password is None:
        password = getpass.getpass(prompt='Input your github password : ')

    account = GithubAccount(username, password)
    GithubBackup(account).backup(backup_name = output, compress = compress)

if __name__ == '__main__':
    main()
