import shutil
import os
import tempfile
import zipfile
import glob
import subprocess
import time
from datetime import datetime

from studip_sync.config import CONFIG
from studip_sync.session import Session, DownloadError, LoginError
from studip_sync.parsers import ParserError


class ExtractionError(Exception):
    pass


class StudipSync(object):

    def __init__(self):
        super(StudipSync, self).__init__()
        self.workdir = tempfile.mkdtemp(prefix="studip-sync")
        self.download_dir = os.path.join(self.workdir, "zips")
        self.extract_dir = os.path.join(self.workdir, "extracted")
        self.destination_dir = CONFIG.target

        os.makedirs(self.download_dir)
        os.makedirs(self.extract_dir)
        os.makedirs(self.destination_dir, exist_ok=True)

    def sync(self, sync_fully=False):
        extractor = Extractor(self.extract_dir)
        rsync = RsyncWrapper()

        with Session() as session:
            print("Logging in...")
            try:
                session.login(CONFIG.username, CONFIG.password)
            except (LoginError, ParserError):
                print("Login failed!")
                return 1

            print("Downloading course list...")
            courses = []
            try:
                courses = list(session.get_courses())
            except (LoginError, ParserError):
                print("Downloading course list failed!")
                return 1

            status_code = 0
            for i in range(0, len(courses)):
                course = courses[i]
                print("{}) {}".format(i+1, course["save_as"]))
                try:
                    if sync_fully or session.check_course_new_files(course["course_id"], CONFIG.last_sync):
                        print("\tDownloading files...")
                        zip_location = session.download(
                            course["course_id"], self.download_dir, course.get("sync_only"))
                        extractor.extract(zip_location, course["save_as"])
                    else:
                        print("\tSkipping this course...")
                except DownloadError:
                    print(" Download FAILED!")
                    status_code = 2
                except ExtractionError:
                    print(" Extracting FAILED!")
                    status_code = 2

        print("Synchronizing with existing files...")
        rsync.sync(self.extract_dir + "/", self.destination_dir)

        CONFIG.update_last_sync(int(time.time()))

        return status_code

    def cleanup(self):
        shutil.rmtree(self.workdir)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.cleanup()


# pylint: disable=too-few-public-methods
class RsyncWrapper(object):

    def __init__(self):
        super(RsyncWrapper, self).__init__()
        timestr = datetime.strftime(datetime.now(), "%Y-%m-%d_%H+%M+%S")
        self.suffix = "_" + timestr + ".old"

    def sync(self, source, destination):
        subprocess.call(["rsync", "--recursive", "--checksum", "--backup",
                         "--suffix=" + self.suffix, source, destination], stdout=subprocess.PIPE, stderr=subprocess.PIPE)


class Extractor(object):

    def __init__(self, basedir):
        super(Extractor, self).__init__()
        self.basedir = basedir

    @staticmethod
    def remove_intermediary_dir(extracted_dir):
        def _filter_dirs(base_name):
            return os.path.isdir(os.path.join(extracted_dir, base_name))

        subdirs = list(filter(_filter_dirs, os.listdir(extracted_dir)))
        if len(subdirs) == 1:
            intermediary = os.path.join(extracted_dir, subdirs[0])
            for filename in glob.iglob(os.path.join(intermediary, "*")):
                shutil.move(filename, extracted_dir)
            os.rmdir(intermediary)

    @staticmethod
    def remove_empty_dirs(directory):
        for root, dirs, files in os.walk(directory):
            if not dirs and not files:
                os.rmdir(root)

    @staticmethod
    def remove_filelist(directory):
        filelist = os.path.join(directory, "archive_filelist.csv")
        if os.path.isfile(filelist):
            os.remove(filelist)

    def extract(self, archive_filename, destination, cleanup=True):
        try:
            with zipfile.ZipFile(archive_filename, "r") as archive:
                destination = os.path.join(self.basedir, destination)
                archive.extractall(destination)
                if cleanup:
                    self.remove_filelist(destination)
                    self.remove_intermediary_dir(destination)
                    self.remove_empty_dirs(destination)

                return destination
        except zipfile.BadZipFile:
            raise ExtractionError("Cannot extract file {}".format(archive_filename))

@@ -1089,32 +1089,37 @@
 or, ParserError)
+ as e
 :%0A              
@@ -1135,32 +1135,57 @@
 Login failed!%22)%0A
+                print(e)%0A
                 
@@ -1382,16 +1382,21 @@
 erError)
+ as e
 :%0A      
@@ -1438,32 +1438,57 @@
  list failed!%22)%0A
+                print(e)%0A
                 
@@ -2196,24 +2196,29 @@
 ownloadError
+ as e
 :%0A          
@@ -2234,17 +2234,18 @@
  print(%22
- 
+%5Ct
 Download
@@ -2247,32 +2247,73 @@
 nload FAILED!%22)%0A
+                    print(%22%5Ct%22 + str(e))%0A
                 
@@ -2370,16 +2370,21 @@
 ionError
+ as e
 :%0A      
@@ -2404,17 +2404,18 @@
  print(%22
- 
+%5Ct
 Extracti
@@ -2419,32 +2419,73 @@
 cting FAILED!%22)%0A
+                    print(%22%5Ct%22 + str(e))%0A
                 
@@ -2672,24 +2672,134 @@
 e.time()))%0A%0A
+        wait_time = 5%0A        print(%22Waiting %7B%7D seconds...%22.format(wait_time))%0A        time.sleep(wait_time)%0A%0A
         retu
@@ -3374,16 +3374,22 @@
 backup%22,
+ %22-v%22,
 %0A       
@@ -3457,56 +3457,8 @@
 ion%5D
-, stdout=subprocess.PIPE, stderr=subprocess.PIPE
 )%0A%0A%0A

Improve error output and wait 5 sec to show rsync output