#!/usr/bin/python

import yaml
import re
import os
import subprocess
import ConfigParser
import time
import hashlib
import sys

'''This file will be looked up under OPKG_DIR/conf'''
OPKG_CONF_FILE='/etc/opkg/conf/opkg.env'

META_FILE_PREVIOUS='Previous.meta'
META_FILE_LATEST='Latest.meta'
EXTRA_PARAM_DELIM=','
EXTRA_PARAM_KEY_VAL_SEP='='

''' Classes '''

'''Tracks local env configuration'''
class EnvConfig():
    def __init__(self):
        self.config_file=OPKG_CONF_FILE
        self.conf=None

    def setConfigFile(self,config_file):
        self.config_file=config_file

    def loadConfigFile(self):
        self.conf = ConfigParser.ConfigParser()
        if os.path.isfile(self.config_file) and os.access(self.config_file, os
.R_OK):
            self.conf.read(self.config_file)

    def updateConfigItem(self,section,key,val):
        try:
            self.conf.set(section,key,val)
        except:
            print "Warning: Cannot locate config item "+key+" in "+self.config_file
            return False

        return True

    def getConfigItem(se
lf,section,item):
        return self.conf.get(section,item)

'''Class to read opkg manifest '''
class Manifest():

    def __init__(self, manifest_path):
        self.manifest_file=manifest_path
        self.manifest_dict=None
        self.rel_num=None

        with open(self.manifest_file, 'r') as stream:
            try:
                self.manifest_dict=yaml.load(stream)
            except yaml.YAMLError as exc:
                print "Error: Problem loading manifest file "+self.manifest_file
                print(exc)
                return

        if 'rel_num' not in self.manifest_dict:
            print "rel_num not found in "+self.manifest_file
            self.manifest_dict=None
            return

        self.rel_num=self.manifest_dict['rel_num']

    def getConfig(self):
        return self.manifest_dict

    '''The action lines are available as dictionaries and converting those to a list is easier to deal with down the road.
    It could be a mixed-bag, plain list item or a dict element.
    '''
    def getSectionItems(self, section):
        if section not in self.manifest_dict: return None
        lines = list()
        for item in self.manifest_dict[section]:
            if type(item) is str:
                lines.append(item)
            elif type(item) is dict:
                for key in item:
                    val = item[key]
                    lines.append(key + ':' + val)
                    break
            else:
                print "Error: Unknown format in "+self.manifest_file+", section: "+section
                return None

        return lines

'''Class for core Open Pkg'''
class Pkg():
    def __init__(self,name):
        if re.search("\W", name) is not None:
            print "Error: Illegal character in package name (" + name + ")"
            return
        self.name=name
        self.rel_num=None
        self.rel_ts=None
        self.is_release=False
        self.manifest_file = name + '.yml'
        self.tarball_name = name + '.tgz'
        self.md5=None #md5 of package being installed.
        self.manifest=None
        self.build_root = os.getcwd()
        self.manifest_path=self.build_root+'/'+self.manifest_file

        '''stage_dir - where files to create a tarball are staged and
        files from a tarball are extracted for deployment.
        By default, the stage_dir is set for action create pkg.
        '''
        self.stage_dir = self.build_root + '/.pkg/' + name
        self.deploy_dir=self.stage_dir+'/.deploy'

        self.env_conf=None
        self.install_meta=None #meta data of existing installation
        self.install_md5=None #md5 of currently installed version

    @staticmethod
    def parseName(pkg_label):
        '''pkg can be specified in following ways:
        - /path/to/mypkg.tgz -- tarball available on locally
        - /path/to/mypkg-rel_num.tgz -- tarball available on locally
        - mypkg
        - mypkg-rel_num
        '''
        pkg_name_rel_num = os.path.basename(pkg_label)
        pkg_name_rel_num = pkg_name_rel_num.replace('.tgz', '')
        tarball_name = pkg_name_rel_num + '.tgz'
        pkg_name = re.split('-', pkg_name_rel_num)[0]

        return pkg_name,pkg_name_rel_num,tarball_name

    @staticmethod
    def parseTarballName(tarball_name):
        rel_num, rel_ts = 'dev', None
        '''The dev version will not have any rel_num or rel_ts
        The parsing is based on the assumption that the tarball names can have only 2 formats:
         name.tgz - dev
         name-rel_num-rel_ts.tgz - release
        '''
        m = re.search('.+?-(.+?)-(.+).tgz', tarball_name)
        if m:
            rel_num = m.group(1)
            rel_ts = m.group(2)

        return rel_num,rel_ts

    def setManifest(self,f):
        self.manifest_path=f
    def loadManifest(self):
        self.manifest = Manifest(self.manifest_path)

    def setRelNum(self,rel_num):
        self.rel_num=rel_num
    def setRelTs(self,rel_ts):
        self.rel_ts=rel_ts

    '''Meta file has this syntax: pkg_name,rel_num,rel_ts,pkg_md5,deploy_ts'''
    def loadMeta(self):
        self.install_meta=dict()
        meta_dir=self.env_conf['basic']['opkg_dir'] + '/meta/' + self.name
        meta_path = meta_dir + "/" + META_FILE_LATEST
        self.install_meta['latest_install']=self.loadMetaFile(meta_path)
        if not self.install_meta['latest_install']:
            print "Info: No active installation of "+self.name+" found at "+self.env_conf['basic']['opkg_dir']
        meta_path = meta_dir + "/" + META_FILE_PREVIOUS
        self.install_meta['previous_install'] = self.loadMetaFile(meta_path)
        if not self.install_meta['previous_install']:
            print "Info: No previous installation of "+self.name+" found."
        if self.install_meta['latest_install']:
            self.install_md5 = self.install_meta['latest_install']['pkg_md5']

    def getMeta(self):
        return self.install_meta

    '''Load .meta files that keep track of deployments and verifies the data in those.
    The meta data on package deployment is a single line with attrs delimited by , in the following order:
    pkg_name,pkg_rel_num,pkg_ts,pkg_md5,deploy_ts
    '''
    def loadMetaFile(self,file_path):
        if not os.path.isfile(file_path): return None
        str = loadFile(file_path)
        install_info = str.strip().split(',')
        if len(install_info) < 5: return None
        meta=dict()
        meta['pkg_name']=install_info[0]
        meta['pkg_rel_num'] = install_info[1]
        meta['pkg_ts'] = install_info[2]
        meta['pkg_md5'] = install_info[3]
        meta['deploy_ts'] = install_info[4]

        return meta

    '''Reset install meta files upon successful installation of a package.'''
    def registerInstall(self,deploy_inst):
        meta_dir=deploy_inst.opkg_dir + "/meta/" + self.name
        meta_file_previous = meta_dir + "/" + META_FILE_PREVIOUS
        meta_file_latest = meta_dir + "/" + META_FILE_LATEST
        runCmd("mkdir -p "+meta_dir)

        if os.path.exists(meta_file_latest):
            if not execOSCommand("mv -f " + meta_file_latest + " " + meta_file_previous):
                print "Problem moving " + meta_file_latest + " as " + meta_file_previous
                return False

        '''Meta file has this syntax: pkg_name,rel_num,rel_ts,pkg_md5,deploy_dir'''
        rel_num=''
        if self.rel_num: rel_num=self.rel_num
        rel_ts=0
        if self.rel_ts: rel_ts=self.rel_ts
        strx = self.name+','+rel_num+','+str(rel_ts)+','+self.pkg_md5+','+deploy_inst.deploy_ts
        cmd = "echo " + strx + ">" + meta_file_latest
        if not execOSCommand(cmd):
            print "Error: Couldn't record the package installation."
            return False
        self.loadMeta()

        return True

    def setRelease(self,is_release=True):
        self.is_release=is_release

    def setEnvConfig(self,env_conf):
        self.env_conf=env_conf

    def create(self):
        self.loadManifest() #the default manifest points to that in build dir

        runCmd("mkdir -p " + self.stage_dir)
        os.chdir(self