#     V3m - Copyright 2007 Dolphin Hawkins (dolphin@exitzer0.com)#
#
#     This program 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 2 of the License, or
#     (at your option) any later version.

#     This program 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 this program; if not, write to the Free Software
#     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


from __future__ import with_statement
from sys import argv
from zipfile import *
from shutil import copyfile
import time, os, imp, re, sys, traceback, ConfigParser, Image
from optparse import OptionParser
from msvcrt import getch

# from BitPim
from phones.com_brew import *
from commport import CommConnection
from com_othercdma import Phone
import com_brew


SCRATCH = "V3M.tmp"

MANIFEST = "META-INF/MANIFEST.MF"
INI = "v3m.ini"

MIDLET1 = "MIDlet-1"
MIDLETICON = "MIDlet-Icon"
JARSIZE="MIDlet-Jar-Size"
JARURL="MIDlet-Jar-URL"
CONTENTFOLDER="Content-Folder"
DESCRIPTION="MIDlet-Description"
MIDLETNAME="MIDlet-Name"
MIDLETVENDOR="MIDlet-Vendor"
MIDLETVERSION="MIDlet-Version"


VERBOSE=False

def Log(message):
    if(VERBOSE):
        print(message)

def IsInZip(zipName, filename):
    zf = None
    try:
        zf = ZipFile(zipName, 'r')
        if(filename in zf.namelist()):
            return True
    except Exception:
        pass
    finally:
        if(zf):
            zf.close()
    return False
        
def UncompressedSize(zipName, filename):
    zf = None
    try:
        zf = ZipFile(zipName, 'r')
        return zf.getinfo(filename).file_size
    except Exception:
        pass
    finally:
        if(zf):
            zf.close()
    return 0
    

def ExtractFromZip(zipName, filename):
    zf = None
    try:
        filename=filename.lstrip('/')
        zf = ZipFile(zipName, 'r')
        if(filename in zf.namelist()):
            return zf.read(filename)
    except Exception:
        pass
    finally:
        if(zf):
            zf.close()
    Log("failed to extract "+filename+" from "+zipName)
    return None

def AddToZip(zipName, filename, data):
    zf = None    
    RemoveFromZip(zipName, filename)
    try:
        zf = ZipFile(zipName, 'a')
    
        zf.writestr(filename, data)
        Log("Added "+filename+" to "+zipName)
    except Exception:
        raise
    finally:
        if(zf):
            zf.close()
    
def RemoveFromZip(zipName, filename):
    tmpname = zipName
    old = None
    new = None
    
    try:
        try:
            old = ZipFile(zipName, 'r')
    
            if(filename in old.namelist()):    
                tmpname = SCRATCH
                new = ZipFile(tmpname, 'w')
                for info in old.infolist():
                    if(info.filename != filename):
                        new.writestr(info, old.read(info.filename))
                        
                Log("removed "+filename+" from "+zipName)
        finally:
            if(old != None):
                old.close()
            if(new != None):
                new.close()
    except Exception:
        raise
    else:
        if(tmpname != zipName):
            os.remove(zipName)
            os.rename(tmpname, zipName)
    

def ParseManifest(manifest):
    clean = re.sub("\n[ \t\r]+", "", manifest) 
    parsed = dict(re.findall("^\s*(\S+)\s*:\s*(.*?)\s*$", clean, re.M))
    return parsed
                     
  
def CreateJad(jadDict):
    jad = ""
    for k,v in jadDict.iteritems():
        jad = jad+k+": "+str(v)+"\n"
    return jad


def SizeIcon(jarFile, iconName):
    tmpfile = SCRATCH+".png"
    
    try:
        WriteToFile(tmpfile, ExtractFromZip(jarFile, iconName))
        
        img = Image.open(tmpfile)
        if(img.size[0] != 15 or img.size[1] != 15):
            Log("Resizing icon file "+iconName)
            icon = img.resize((15,15), Image.ANTIALIAS) 
            icon.save(tmpfile)
            AddToZip(jarFile, iconName, ReadFromFile(tmpfile))
    finally:
        if(img and img.fp): 
            img.fp.close()
        os.remove(tmpfile)

        
def CheckIcon(jarFile, jadTags, iconFile):
    Assert(jadTags.has_key(MIDLET1), "Bad manifest file")
    
    midletList = jadTags[MIDLET1].split(',')
    icon = midletList[1].strip().lstrip('/')
    
    midletIcon = ( jadTags.has_key(MIDLETICON) and jadTags[MIDLETICON].strip().lstrip('/')) or icon

    writeIcon = None
    
    if(icon == midletIcon):
        if(icon != "" and  not IsInZip(jarFile, icon)):
            Log("Icon "+ icon+ " is not in "+ jarFile)
            writeIcon = icon
        jadTags[MIDLETICON]=icon
    else:
        if(icon != ""):
            Log("Modifying "+MIDLETICON +" to agree with "+MIDLET1)
            jadTags[MIDLETICON]=icon
            if(not IsInZip(jarFile, icon)):
                Log("Icon "+ icon+ " is not in "+ jarFile)
                writeIcon = icon            
        elif(midletIcon != ""):
            Log("Modifying "+MIDLET1 +" to agree with "+MIDLETICON)
            midletList[1]=midletIcon
            jadTags[MIDLET1] =  ','.join(midletList)
            if(IsInZip(jarFile, midletIcon)):
                Log("Icon "+ midletIcon+ " is not in "+ jarFile)
                writeIcon = midletIcon
              
        Assert(os.access(jarFile, os.X_OK), MIDLETICON+" and "+MIDLET1+ " do not agree.")
        AddToZip(jarFile, MANIFEST, CreateJad(jadTags))
     
    if(writeIcon != None):
        Assert(os.access(jarFile, os.X_OK), "Unable to add icon to "+ jarFile)
        Assert(os.access(iconFile, os.R_OK), "Unable to read icon file "+ iconFile)
        AddToZip(jarFile, writeIcon, ReadFromFile(iconFile))
    
    if(jadTags[MIDLETICON] != ""):
        SizeIcon(jarFile, jadTags[MIDLETICON])
        
    if(not jadTags.has_key(MIDLETNAME)):
        jadTags[MIDLETNAME]=midletList[0]
        
    
def WriteToFile(filename, buffer):
    with file(filename, 'wb') as fh:
        fh.write(buffer)
    
def ReadFromFile(filename):
    with file(filename, 'rb') as fh:
        return fh.read()

def FileSize(filename):
    return os.stat(filename)[6]    
        
    
def Assert(exp, message):
    if not exp:
        raise Exception(message)
    
        
def ProcessJar(jarFile, id, iconFile):
    currentDir = os.path.abspath(os.path.curdir)+os.path.sep
    print("\nProcessing "+ jarFile+ " ->"+ currentDir+str(id) + ".jad")
    Assert(os.access(jarFile, os.R_OK), "Unable to find the file "+jarFile)
    
    manifest = ExtractFromZip(jarFile, "META-INF/MANIFEST.MF")
    Assert(manifest != None, "Unable to extract manifest from "+jarFile)
           
    jarID = str(id)+".jar"
    if(jarID != jarFile):
        copyfile(jarFile, jarID)
    jadTags = ParseManifest(manifest)
    
    CheckIcon(jarID, jadTags, iconFile)    
    
    jadTags[JARSIZE]=FileSize(jarID)
    jadTags[JARURL] = jarID
    jadTags[CONTENTFOLDER]="Games"
    
    for tag in [DESCRIPTION, MIDLETNAME, MIDLETVENDOR, MIDLETVERSION]:
        if(not jadTags.has_key(tag)):
            jadTags[tag]=""
        
            
    WriteToFile(str(id)+".jad", CreateJad(jadTags))
    print("Completed "+ str(id) + ".jad\n")
        
    return

def SaveConfig(exedir,config):
    with open(exedir+"v3m.ini", 'w') as ini:
        config.write(ini)


def LoadConfig(exedir):
    ConfigParser.DEFAULTSECT = "DEFAULT"
    config = ConfigParser.ConfigParser({'action':'none', 'comport':'0', 'category':'Games', 'icon':'iconBoo.png', 'output':".", 'quit':'False', 'continue':'False'})
    config.read(exedir+"v3m.ini")
    return config


def ProcessCommandLine(config):
    global VERBOSE
    
    parser = OptionParser("usage: %prog [options] jarFiles\nWhere jarFiles can be the names of jarfiles and/or a directory containing jarfiles to be converted.")
    
    parser.add_option("-c", "--category", help="The name of the category to use, the default is 'Games' (experimental)",                         default=config.get("DEFAULT", "category"))
    parser.add_option("-i", "--icon",   help="The filename of an icon to use if there is not one in the archive", default=config.get("DEFAULT", "icon"))
    parser.add_option("-o", "--output",   help="The name of the directory to put the output files.  Relative path names are relative to the FIRST jar file.", default=config.get("DEFAULT", "output"))
    parser.add_option("-v", "--verbose",   help="Show extra output as to what the script is doing", action="store_true", default=VERBOSE)
    parser.add_option("-q", "--quit",   help="Do not pause at the end of processing", action="store_true", default=config.getboolean("DEFAULT", "quit"))
    parser.add_option("-k", "--continue",   help="Do not pause on errors", action="store_true", default=config.getboolean("DEFAULT", "continue"))
    parser.add_option("-p", "--port",   help="Comport to use for installing the files.  See to 0 to disable phone communication", default = config.getint("DEFAULT", 'comport'), type="int")
    parser.add_option("-a", "--action", help="Action to take after converting files successfully.  Can be [none|copy|install|verify]", default=config.get("DEFAULT", "action"), choices=('none', 'copy', 'install', 'verify'))
    (options, args) = parser.parse_args()
    
    VERBOSE  = options.verbose or len(args) == 0
    
    if(len(args) == 0):
        Log("Saving options and quitting")
    
    
    for k,v in config.items("DEFAULT"):
        if(hasattr(options, k)):
            config.set("DEFAULT", k, str(getattr(options, k)))
            
        Log(k+": "+str(v))
        
  
    
    parser.destroy()
    return args
        
def CheckJarNumber(directory, number):
    if(os.path.exists(directory+str(number)+".jar") or
       os.path.exists(directory+str(number)+".jad")):
       return False
    return True

def GetNextJarNumber(directory, number):
    while(not CheckJarNumber(directory, number)):
        number+=1
    
    return number

# from http://www.py2exe.org/index.cgi/HowToDetermineIfRunningFromExe
def main_is_frozen():
   return (hasattr(sys, "frozen") or # new py2exe
           hasattr(sys, "importers") # old py2exe
           or imp.is_frozen("__main__")) # tools/freeze

def get_main_dir():
   if main_is_frozen():
       return os.path.dirname(os.path.abspath(sys.executable))
   return os.path.dirname(sys.argv[0])

def pause(message ="Press any key to continue..."):
    print(message)
    getch()
    

def MatchedJad(file):
    try:
        file = os.path.basename(file.lower())
        if(file.endswith(".jad")):
            seperator = "".rindex
            number = int(file[:-4])
            jar = str(number)+".jar"
            if(os.path.exists(jar)):
                return True
    except:
        return False
    
def BuildV3mList(phone, directory):
    fileList = {}
    dirlist = phone.getfilesystem(directory)
    
    for fileInfo in dirlist.values():
        name=os.path.basename(fileInfo['name'].lower())
        number = name[:-4]
        if(name.endswith(".jad")):
            data = phone.getfilecontents(fileInfo['name'])
            tags = ParseManifest(data)
            fileList[tags[MIDLETNAME]]={'size':fileInfo['size'], 'name':tags[MIDLETNAME],'number':number}
            
    return fileList
    
    
def BuildCopyList():
    fileList = {}
    
    for file in filter(MatchedJad, os.listdir(".")):
        number = file[:-4]
        with open(file, 'r') as jad:
            tags = ParseManifest(jad.read())
            if(tags.has_key(MIDLETNAME)):
                fileList[tags[MIDLETNAME]]= {'size':FileSize(file), 'name':tags[MIDLETNAME], 'number':number}
                
    return fileList

class DummyLog:
    def log(self, s):
        pass
    def logdata(self, s, data, klass):
        pass
    def progress(self, pos, max, desc):
        dot()
            
        
def ConnectToPhone(port, throw):
    logger = DummyLog()
    retry = True
    while(retry):
        try:
            return Phone(logger, CommConnection(logger, port))            
        except Exception, e:
            if(throw):
                raise e
            sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
            traceback.print_exc()
            print "Unable to connect to the phone, Retry (y/n)?",
            entry = None
            while(entry !='y' and entry != 'n'):
                time.sleep(0.1)
                entry = getch().lower()
            print entry
            if(entry == 'n'):
                raise e

def PushFiles(phone, fileList):
    base = "/motorola/shared/jas/temp/install/"
    for name, fileInfo in fileList.items():
        try:
            number = fileInfo['number']
            Log("\nCopying %s (%s)"%(name, number))
            jarFile = ReadFromFile(number+".jar")
            jadFile = ReadFromFile(number+".jad")
            
            phone.writefile(base+number+".jar", jarFile)
            phone.writefile(base+number+".jad", jadFile)
        except:
            print("Failed to write "+number)
            
def dot():
    if(VERBOSE):
        sys.stdout.write('.')
        sys.stdout.flush()

            
def ProcessAction(action, port):
    phone=None
    try:
        if(action=='none' or port == None):
            return
        
        phone = ConnectToPhone(port, False)
       
        waiting = BuildV3mList(phone, '/motorola/shared/jas/temp/install')    
        Assert(len(waiting.items()) == 0, "Install directory needs to be empty before continuing")
            
        built=BuildCopyList()
        PushFiles(phone, built)
        
        if(action =='copy'):
            return
       
        #installed = BuildV3mList(phone, '/motorola/shared/jas/content')
        
        phone.offlinerequest(True, 0)
        
        if(action == 'install'):
            return
        
        
        phone = None
        
        Log("\nWaiting for phone to restart")
        
        for n in range(5):
            time.sleep(1)
            dot()
        
        for n in range(10):
            try:
                time.sleep(1)
                phone = ConnectToPhone(port, True)
                break
            except:
                dot()
    
        
        Assert(phone != None, "Unable to reconnect to the phone")
        
        # try a bit to give it time to install
        for d in range(5):
            dot()
            waiting = BuildV3mList(phone, '/motorola/shared/jas/temp/install')    
            if(len(waiting.items()) == 0):
                break
            
        nowInstalled = BuildV3mList(phone, '/motorola/shared/jas/content')
        
        print
        
        for name,v in built.items():
            if(nowInstalled.has_key(name)):
                Log("Successfully installed %s" % name)
                added = built[name]['number']
                os.remove(added+".jar")
                os.remove(added+".jad")
            else:
                Log("Failed to install %s" % name)
        
    finally:
        if(phone != None):
            phone.close()
    
    
def main():
    quit=False
    try:
        exedir = get_main_dir()+os.path.sep
        config = LoadConfig(exedir)

        quit= config.getboolean("DEFAULT", "quit")
        cont= config.getboolean("DEFAULT", "continue")
        action = config.get("DEFAULT", "action")
        port = config.getint("DEFAULT", "comport")
        if(port == 0):
            port=None
        else:
            port="COM"+str(port)
        
        
        
        if(os.path.isabs(config.get("DEFAULT", "icon"))):
            iconFile = config.get("DEFAULT", "icon")
        else:
            iconFile = exedir+config.get("DEFAULT", "icon")
        
        jarFilesRel = ProcessCommandLine(config)
        jarFiles = []
        for jarFile in jarFilesRel:
            abs= os.path.abspath(jarFile)
            if(os.path.isdir(abs)):
                for file in os.listdir(abs):
                    if(file.lower().endswith(".jar")):
                        jarFiles.append(abs+os.path.sep+file)
            else:
                jarFiles.append(abs)
        
        if(len(jarFiles) > 0):
            os.chdir(os.path.dirname(os.path.abspath(jarFiles[0])))
            
            outputDir = config.get("DEFAULT", "output")
            
            if(not os.path.exists(outputDir)):                
                os.makedirs(outputDir)
                
            os.chdir(outputDir)
            
            number = 1
            for jarFile in jarFiles:
                try:
                    number = GetNextJarNumber("."+os.path.sep, number)
                    ProcessJar(jarFile, number, iconFile)
                except Exception, e:
                    sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
                    traceback.print_exc()
                    try:
                        os.remove(str(number)+".jar")
                    except:
                        pass
                    try:
                        os.remove(str(number)+".jad")
                    except:
                        pass
                    if(not cont ):
                        pause()
        
        ProcessAction(action, port)
        
        SaveConfig(exedir,config)
        print("Done!")
    
    except Exception, e:
        sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
        traceback.print_exc()
    
    if(not quit and main_is_frozen()):
        pause("Press any key to exit...")

if __name__ == "__main__":
    sys.exit(main())