# -*- coding: utf-8 -*-

# Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from calculate.lib.utils.files import (readLinesFile, readFile, pathJoin,
                                       process)
from calculate.lib.utils.portage import reVerSplit
from os import path
import os
import glob
import re
import hashlib


class PkgContents:
    """
    Object for work with CONTENTS file
    """
    reCfg = re.compile(r"/\._cfg\d{4}_")
    reObj = re.compile(
        r"^(?:"
        r"(?:(?P<sym>sym)\s+(?P<symname>.*)\s+->"
        r"\s+(?P<symtarget>.*)\s+(?P<symtime>\S+)\s*)|"
        r"(?:(?P<dirfif>dir|fif)\s+(?P<dirname>.*)\s*)|"
        r"(?:(?P<obj>obj)\s+(?P<filename>.*)\s+"
        r"(?P<md5>\S+)\s+(?P<filetime>\S+)\s*))$")

    def __init__(self, pkg, prefix="/"):
        self.prefix = prefix
        self.contentFile = path.join(prefix, 'var/db/pkg/%s/CONTENTS' % pkg)
        self.readContents()

    def _identifyLine(self, line):
        res = self.reObj.search(line.strip())
        if res:
            res = res.groupdict()
            if res.get('dirfif', ''):
                return res['dirname'], {'type': res['dirfif']}
            elif res.get('obj', ''):
                return (res['filename'], {'type': "obj",
                                          'md5': res['md5'],
                                          'mtime': res['filetime']})
            elif res.get('sym', ''):
                return (res['symname'], {'type': 'sym',
                                         'target': res['symtarget'],
                                         'mtime': res['symtime']})

    def readContents(self):
        """
        Re-read contents
        """
        self.content = dict(filter(None, map(self._identifyLine,
                                             readLinesFile(self.contentFile))))

    def writeContents(self):
        """
        Write current content
        """
        f = open(self.contentFile, 'w')
        for filename in sorted(
                self.content.keys(),
                key=lambda x: (1 if x.startswith('/etc') else 0, x)):
            value = self.content[filename]
            formats = {'obj': "obj {filename} {md5} {mtime}\n",
                       'sym': "sym {filename} -> {target} {mtime}\n",
                       'dir': "dir {filename}\n",
                       'fif': "fif {filename}\n"}
            f.write(formats[value['type']].format(filename=filename, **value))
        f.close()

    def addDir(self, filename):
        filename = self.reCfg.sub("/", filename)
        if filename != '/':
            if (filename not in self.content or
                        self.content[filename]['type'] != 'dir'):
                self.addDir(path.dirname(filename))
                self.content[filename] = {'type': 'dir'}

    def addFile(self, filename):
        newfilename = pathJoin(self.prefix, filename)
        filename = self.reCfg.sub("/", filename)
        self.content[filename] = {'type': 'obj',
                                  'md5': hashlib.md5(
                                      readFile(newfilename)).hexdigest(),
                                  'mtime': str(
                                      int(os.stat(newfilename).st_mtime))}

    def addLink(self, filename):
        newfilename = pathJoin(self.prefix, filename)
        filename = self.reCfg.sub("/", filename)
        self.content[filename] = {'type': 'sym',
                                  'target': os.readlink(newfilename),
                                  'mtime': str(
                                      int(os.lstat(newfilename).st_mtime))}

    def _fixNameByPrefix(self, filename):
        if self.prefix != '/' and filename.startswith(self.prefix):
            return filename[len(self.prefix):]
        return filename

    def origFileName(self, filename):
        return self.reCfg.sub("/", filename)

    def removeObject(self, filename):
        filename = self._fixNameByPrefix(filename)
        filename = self.reCfg.sub("/", filename)
        if filename in self.content:
            self.content.pop(filename)

    def addObject(self, filename):
        """
        Add object to content
        """
        if filename != '/':
            filename = self._fixNameByPrefix(filename)
            newfilename = pathJoin(self.prefix, filename)
            self.addDir(path.dirname(filename))
            if path.islink(newfilename):
                self.addLink(filename)
            elif path.isdir(newfilename):
                self.addDir(filename)
            elif path.isfile(newfilename):
                self.addFile(filename)


def checkReserved(fileName, contentFile):
    """
    Check contents with newContent
    """
    TYPE, FILENAME, MD5, MTIME = 0, 1, 2, 3
    obj = filter(lambda x: x[1] == fileName,
                 map(lambda x: x.split(' '),
                     filter(lambda x: x.startswith('obj'),
                            readLinesFile(contentFile))))
    # if pkg not content filename
    if not obj:
        return True
    # if file is not exists
    if not path.exists(fileName):
        return True
    contentMD5 = hashlib.md5(readFile(fileName)).hexdigest().strip()
    configMD5 = obj[0][MD5].strip()
    # if content was not changed
    if contentMD5 == configMD5:
        return True
    return False


def checkContents(pkg, fileName, prefix='/', reservedFile=None):
    """
    Check contents with newContent
    """
    contentFile = path.join(prefix, 'var/db/pkg/%s/CONTENTS' % pkg)
    if prefix != '/' and fileName.startswith(prefix):
        shortName = fileName[len(prefix):]
    else:
        shortName = fileName

    TYPE, FILENAME, MD5, MTIME = 0, 1, 2, 3
    obj = filter(lambda x: x[1] == shortName,
                 map(lambda x: x.split(' '),
                     filter(lambda x: x.startswith('obj'),
                            readLinesFile(contentFile))))
    # if pkg not content filename
    if not obj:
        # for using reserved -CONTENTS file on postinst
        if not reservedFile or checkReserved(fileName, reservedFile):
            return True
        else:
            return False
    # if file is not exists
    if not path.exists(fileName):
        # for using reserved -CONTENTS file on postinst
        if not reservedFile or checkReserved(fileName, reservedFile):
            return True
        else:
            return False
    contentMD5 = hashlib.md5(readFile(fileName)).hexdigest().strip()
    configMD5 = obj[0][MD5].strip()
    # if content was not changed
    if contentMD5 == configMD5:
        return True
    return False


def getCfgFiles(protected_dirs=('/etc',), prefix='/'):
    """
    Get protected cfg files
    """
    reCfg = re.compile(r"/\._cfg\d{4}_", re.S)
    findParams = ["find"] + map(lambda x: pathJoin(prefix, x),
                                protected_dirs) + [
                     "-name", "._cfg????_*", "!", "-name", ".*~", "!", "-iname",
                     ".*.bak", "-printf", r"%T@ %p\n"]
    mapCfg = {}
    for filetime, sep, filename in map(lambda x: x.partition(' '),
                                       filter(None, process(*findParams))):
        origFilename = reCfg.sub(r'/', filename)
        if not origFilename in mapCfg:
            mapCfg[origFilename] = []
        mapCfg[origFilename].append((int(filetime.split('.')[0]), filename))
    return mapCfg


def fillContents(allContent, protected, prefix='/'):
    """
    Fill dict file - package
    """
    dbPath = pathJoin(prefix, 'var/db/pkg')
    for contentFile in glob.glob(dbPath + "/*/*/CONTENTS"):
        for objFile in filter(lambda x: x.startswith('obj '),
                              readLinesFile(contentFile)):
            res = PkgContents.reObj.search(objFile.strip())
            if res:
                fn = res.groupdict()['filename']
                if filter(lambda x: fn.startswith(x), protected):
                    pkg = reVerSplit.search(os.path.dirname(contentFile))
                    if pkg:
                        pkg = "%s/%s" % (pkg.groups()[:2])
                        allContent[fn] = pkg
