'''
NFI -- Silensec's Nyuki Forensics Investigator

Copyright (C) 2014  George Nicolaou (george[at]silensec[dot]com)
                    Silensec Ltd.

This file is part of Nyuki Forensics Investigator (NFI).

NFI is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

NFI is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with NFI.  If not, see <http://www.gnu.org/licenses/>.
'''
from IApp import IApp
from MimeGuesser import MimeGuesser
import ExtractStore
import os
from os import listdir
from os.path import isfile, isdir, islink, join, basename, dirname
from MiscUtils import genfilesig, DEFAULT_HASHER
from ModuleImporter import Importer

class ApplicationParser(object):
    
    parser_name = "Android Application Parser"
    appstore = {}
    exstore = None        
    def __init__(self, outqueue=None, extract_store=None, mountpoint=None, 
                 settings=None, versions=None ):
        self.mimguess = MimeGuesser()
        self.outqueue = outqueue
        self.settings = settings
        self.versions = versions
        imp = Importer()
        app_modules = imp.get_package_modules("AndroidApps", IApp())
        for app in app_modules:
            name = app.get_packagename()
            if name in self.appstore:
                self.appstore[name].append(app)
            else:
                self.appstore[name] = [app]
        if extract_store != None:
            self.exstore = extract_store
        else:
            self.exstore = ExtractStore.ExtractStore()
           
    def get_extractedstore(self):
            return self.exstore
        
    def get_package_list(self):
        return self.appstore.keys()
    
    def get_package(self, name):
        if name not in self.appstore:
            return None
        if len(self.appstore[name]) == 1:
            if self.appstore[name][0].has_defaultversion() == False:
                self.outqueue.put("Package {} is not default".format(name))
            return self.appstore[name][0]
        
        """
        If multiple entries for the same package exist, then we need to decide
        which one we should use
        """
        default = None
        appversion = self.versions.get_application_version(name)
        if appversion == None or appversion == "N/A":
            self.outqueue.put("{} could not determine App version".format(name))
        else:
            try:
                appversion = int(appversion)
            except:
                pass
        for defn in self.appstore[name]:
            if defn.has_defaultversion:
                if default != None: 
                    self.outqueue.put("{} has more than one default".format(name))
                default = defn
            if defn.has_version(appversion):
                return defn
        return default
    
    def handle_file(self, filename, dirpath, app):
        filepath = join(dirpath,filename)
        handler = self.mimguess.get_handler(filepath)
        knowninfo = app.get_file_info(filename)
        fobject = None
        if handler != None:
            fobject = handler(filename,filepath,knowninfo,self.outqueue,
                              self.settings)
        else:
            """
            This is a remote case where no handler can handle the file.
            However, the default handler for unknown files should always be the
            raw data handler
            """
            fobject = ExtractStore.ApplicationFile(filename,
                                                   ExtractStore.TYPE_NONE)
        fobject.set_mime(self.mimguess.get_filemime(filepath))
        fobject.add_sig(genfilesig(filepath, DEFAULT_HASHER))
        fobject.add_stats(os.stat(filepath))
        return fobject
        
        
    
    def scan_single_app_dir(self, dirroot, app, dirobject, store_app):
        for f in listdir(dirroot):
            filepath = join(dirroot,f)
            if isdir(filepath): 
                subdirobject = dirobject.add_dir(f)
                self.scan_single_app_dir(filepath, app, subdirobject, store_app)
            elif islink(filepath):
                """Skip for now"""
                continue
            elif isfile(filepath):
                try:
                    dirobject.add_file(self.handle_file(f, dirroot, app))
                    store_app.totalfiles += 1
                except:
                    print("Error handling file %(fname)s" % {'fname': join(dirroot,f)})
        return
    
    
    def read_link(self, linkpath):
        target_path = os.readlink(linkpath)
        if os.path.isabs(target_path):
            return target_path
        return join(dirname(linkpath),target_path)
    
    def scan_single_app(self, approot_path, app):
        #print "[APP]: " + app.get_packagename()
        if self.outqueue != None:
            self.outqueue.put("[APP]: " + app.get_packagename())
        store_app = self.exstore.create_application(
                                app.get_packagename(), app.get_canonicalname())
        rootdir = store_app.add_directory(approot_path)
        store_app.add_root_stats( os.stat(approot_path) )
        for f in listdir(approot_path):
            filepath = join(approot_path,f)
            if isdir(filepath):
                dirobject = rootdir.add_dir(f)
                self.scan_single_app_dir(filepath, app, dirobject, store_app)
            elif islink(filepath):
                if filepath.endswith("lib"):
                    store_app.set_library_path(self.read_link(filepath))
            elif isfile(filepath):
                store_app.totalfiles += 1
                rootdir.add_file(self.handle_file(f,approot_path,app))
        return
    
    def scan_directory(self, root):
        for dfile in os.listdir(root):
            fqname = join(root,dfile)
            if isdir( fqname ):
                app = self.get_package( dfile )
                if app == None:
                    app = IApp()
                    app.set_packagename( dfile )
                self.scan_single_app( fqname, app )
            else:
                print "Stray file found: " + fqname
                #self.handle_file(fqname, None)
        if self.outqueue != None:
            self.outqueue.put("Scanned Total Apps: " + str(len(self.exstore.store)) )