import os
import grp
import pwd
import logging
import stat
import subprocess
import traceback

from subprocess import Popen, PIPE
from airtimemetadata import AirtimeMetadata
from api_clients import api_client
import pyinotify

class MediaMonitorCommon:

    timestamp_file = "/var/tmp/airtime/media-monitor/last_index"

    def __init__(self, airtime_config, wm=None):
        self.supported_file_formats = ['mp3', 'ogg']
        self.logger = logging.getLogger()
        self.config = airtime_config
        self.md_manager = AirtimeMetadata()
        self.wm = wm

    def is_parent_directory(self, filepath, directory):
        filepath = os.path.normpath(filepath)
        directory = os.path.normpath(directory)
        return (directory == filepath[0:len(directory)])

    def is_temp_file(self, filename):
        info = filename.split(".")
        
        # if file doesn't have any extension, info[-2] throws exception
        # Hence, checking length of info before we do anything
        if(len(info) >= 2):
            if(info[-2] in self.supported_file_formats):
                return True
            else:
                return False
        else:
            return False

    def is_audio_file(self, filename):
        info = filename.split(".")

        if(info[-1] in self.supported_file_formats):
            return True
        else:
            return False

    #check if file is readable by "nobody"
    def has_correct_permissions(self, filepath, euid='nobody', egid='nogroup'):

        try:
            uid = pwd.getpwnam(euid)[2]
            gid = grp.getgrnam(egid)[2]
            
            #drop root permissions and become "nobody"
            os.setegid(gid)
            os.seteuid(uid)

            open(filepath)
            readable = True
        except IOError:
            self.logger.warn("File does not have correct permissions: '%s'", filepath)
            readable = False
            self.logger.error("traceback: %s", traceback.format_exc())
        except Exception, e:
            self.logger.error("Unexpected exception thrown: %s", e)
            readable = False
            self.logger.error("traceback: %s", traceback.format_exc())
        finally:
            #reset effective user to root
            os.seteuid(0)
            os.setegid(0)

        return readable

    # the function only changes the permission if its not readable by www-data
    def set_needed_file_permissions(self, item, is_dir):
        try:
            omask = os.umask(0)
            if not self.has_correct_permissions(item, 'www-data', 'www-data') or not self.has_correct_permissions(item, 'pypo', 'pypo'):
                # stats.st_mode is the original permission
                # stat.S_IROTH - readable by all permission
                # stat.S_IXOTH - excutable by all permission
                # try to set permission 
                if self.is_parent_directory(item, self.config.storage_directory) or self.is_parent_directory(item, self.config.imported_directory) or self.is_parent_directory(item, self.config.organize_directory): 
                    if is_dir is True:
                        os.chmod(item, 02777)
                    else:
                        os.chmod(item, 0666)
                else :
                    # add world readable permission
                    stats = os.stat(item)
                    if is_dir is True:
                        bitor = stats.st_mode | stat.S_IROTH | stat.S_IXOTH
                    else:
                        bitor = stats.st_mode | stat.S_IROTH
                    os.chmod(item, bitor)
        except Exception, e:
            self.logger.error("Failed to change file's owner/group/permissions. %s", e)
            self.logger.error("traceback: %s", traceback.format_exc())
            return False
        finally:
            os.umask(omask)
            return True


    #checks if path is a directory, and if it doesnt exist, then creates it.
    #Otherwise prints error to log file.
    def ensure_is_dir(self, directory):
        try:
            omask = os.umask(0)
            if not os.path.exists(directory):
                os.makedirs(directory, 02777)
                self.wm.add_watch(directory, pyinotify.ALL_EVENTS, rec=True, auto_add=True)
            elif not os.path.isdir(directory):
                #path exists but it is a file not a directory!
                self.logger.error("path %s exists, but it is not a directory!!!")
        finally:
            os.umask(omask)

    #moves file from source to dest but also recursively removes the
    #the source file's parent directories if they are now empty.
    def move_file(self, source, dest):

        try:
            omask = os.umask(0)
            os.rename(source, dest)
        except Exception, e:
            self.logger.error("failed to move file. %s", e)
            self.logger.error("traceback: %s", traceback.format_exc())
        finally:
            os.umask(omask)

        dir = os.path.dirname(source)
        self.cleanup_empty_dirs(dir)

    #keep moving up the file hierarchy and deleting parent
    #directories until we hit a non-empty directory, or we
    #hit the organize dir.
    def cleanup_empty_dirs(self, dir):
        if os.path.normpath(dir) != self.config.organize_directory:
            if len(os.listdir(dir)) == 0:
                try:
                    os.rmdir(dir)
                except Exception, e:
                    #non-critical exception because we probably tried to delete a non-empty dir.
                    #Don't need to log this, let's just "return"
                    return
                
                pdir = os.path.dirname(dir)
                self.cleanup_empty_dirs(pdir)


    #checks if path exists already in stor. If the path exists and the md5s are the
    #same just overwrite.
    def create_unique_filename(self, filepath, old_filepath):

        try:
            if(os.path.exists(filepath)):
                self.logger.info("Path %s exists", filepath)

                self.logger.info("Checking if md5s are the same.")
                md5_fp = self.md_manager.get_md5(filepath)
                md5_ofp = self.md_manager.get_md5(old_filepath)

                if(md5_fp == md5_ofp):
                    self.logger.info("Md5s are the same, moving to same filepath.")
                    return filepath

                self.logger.info("Md5s aren't the same, appending to filepath.")
                file_dir = os.path.dirname(filepath)
                filename = os.path.basename(filepath).split(".")[0]
                #will be in the format .ext
                file_ext = os.path.splitext(filepath)[1]
                i = 1;
                while(True):
                    new_filepath = '%s/%s(%s)%s' % (file_dir, filename, i, file_ext)
                    self.logger.error("Trying %s", new_filepath)

                    if(os.path.exists(new_filepath)):
                        i = i+1;
                    else:
                        filepath = new_filepath
                        break

        except Exception, e:
             self.logger.error("Exception %s", e)

        return filepath

    #create path in /srv/airtime/stor/imported/[song-metadata]
    def create_file_path(self, original_path, orig_md):

        storage_directory = self.config.storage_directory

        is_recorded_show = False

        try:
            #will be in the format .ext
            file_ext = os.path.splitext(original_path)[1]
            file_ext = api_client.encode_to(file_ext, 'utf-8')

            path_md = ['MDATA_KEY_TITLE', 'MDATA_KEY_CREATOR', 'MDATA_KEY_SOURCE', 'MDATA_KEY_TRACKNUMBER', 'MDATA_KEY_BITRATE']

            md = {}
            for m in path_md:
                if m not in orig_md:
                    md[m] = api_client.encode_to(u'unknown', 'utf-8')
                else:
                    #get rid of any "/" which will interfere with the filepath.
                    if isinstance(orig_md[m], basestring):
                        md[m] = orig_md[m].replace("/", "-")
                    else:
                        md[m] = orig_md[m]

            if 'MDATA_KEY_TRACKNUMBER' in orig_md:
                #make sure all track numbers are at least 2 digits long in the filepath.
                md['MDATA_KEY_TRACKNUMBER'] = "%02d" % (int(md['MDATA_KEY_TRACKNUMBER']))

            #format bitrate as 128kbps
            md['MDATA_KEY_BITRATE'] = str(md['MDATA_KEY_BITRATE']/1000)+"kbps"

            filepath = None
            #file is recorded by Airtime
            #/srv/airtime/stor/recorded/year/month/year-month-day-time-showname-bitrate.ext
            if(md['MDATA_KEY_CREATOR'] == api_client.encode_to("Airtime Show Recorder", 'utf-8')):
                #yyyy-mm-dd-hh-MM-ss
                y = orig_md['MDATA_KEY_YEAR'].split("-")
                filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, api_client.encode_to("recorded", 'utf-8'), y[0], y[1], orig_md['MDATA_KEY_YEAR'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)

                #"Show-Title-2011-03-28-17:15:00"
                title = md['MDATA_KEY_TITLE'].split("-")
                show_hour = title[0]
                show_min = title[1]
                show_sec = title[2]
                show_name = '-'.join(title[3:])

                new_md = {}
                new_md["MDATA_KEY_FILEPATH"] = original_path
                new_md['MDATA_KEY_TITLE'] = '%s-%s-%s:%s:%s' % (show_name, orig_md['MDATA_KEY_YEAR'], show_hour, show_min, show_sec)
                self.md_manager.save_md_to_file(new_md)

            elif(md['MDATA_KEY_TRACKNUMBER'] == api_client.encode_to(u'unknown', 'utf-8')):
                filepath = '%s/%s/%s/%s/%s-%s%s' % (storage_directory, api_client.encode_to("imported", 'utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
            else:
                filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, api_client.encode_to("imported", 'utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)

            filepath = self.create_unique_filename(filepath, original_path)
            self.logger.info('Unique filepath: %s', filepath)
            self.ensure_is_dir(os.path.dirname(filepath))

        except Exception, e:
            self.logger.error('Exception: %s', e)

        return filepath

    def exec_command(self, command):
        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
        stdout, stderr = p.communicate()
        if p.returncode != 0:
            self.logger.warn("command \n%s\n return with a non-zero return value", command)
            self.logger.error(stderr)
            
        try:
            """
            File name charset encoding is UTF-8.  
            """
            stdout = stdout.decode("UTF-8")
        except Exception, e:
            self.logger.error("Could not decode %s using UTF-8" % stdout)
            
        return stdout

    def scan_dir_for_new_files(self, dir):
        command = 'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable' % dir.replace('"', '\\"')
        self.logger.debug(command)
        stdout = self.exec_command(command)
        
        return stdout.splitlines()

    def touch_index_file(self):
        dirname = os.path.dirname(self.timestamp_file)
        try:
            if not os.path.exists(dirname):
                os.makedirs(dirname)
            open(self.timestamp_file, "w")
        except Exception, e:
            self.logger.error('Exception: %s', e)
            self.logger.error("traceback: %s", traceback.format_exc())

    def organize_new_file(self, pathname):
        self.logger.info("Organizing new file: %s", pathname)
        file_md = self.md_manager.get_md_from_file(pathname)

        if file_md is not None:
            filepath = self.create_file_path(pathname, file_md)

            self.logger.debug("Moving from %s to %s", pathname, filepath)
            self.move_file(pathname, filepath)
        else:
            filepath = None
            self.logger.warn("File %s, has invalid metadata", pathname)

        return filepath
        
def test_file_playability(pathname):
    """
    Test if the file can be played by Liquidsoap. Return "True" if Liquidsoap
    can play it, or if Liquidsoap is not found. 
    """
    liquidsoap_found = subprocess.call("which liquidsoap", shell=True)
    if liquidsoap_found == 0:
        #return_code = subprocess.call("liquidsoap -r \"%s\"" % pathname.replace('"', '\\"'), shell=True)
        
        #when there is an single apostrophe inside of a string quoted by apostrophes, we can only escape it by replace that apostrophe
        #with '\''. This breaks the string into two, and inserts an escaped single quote in between them.
        command = "liquidsoap -c 'output.dummy(single(\"%s\"))'" % pathname.replace("'", "'\\''")
        return_code = subprocess.call(command, shell=True)
    else:
        return_code = 0

    return (return_code == 0)