#!/usr/bin/env python2
#-*- coding: utf-8 -*-

# Copyright 2015 Calculate Ltd. 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 __future__ import print_function
import argparse
import sys
import re
import os
from os import path
from itertools import chain, groupby
import time

from calculate.lib.datavars import DataVars
from calculate.lib.cl_template import templateFunction
from calculate.lib.utils.files import (readFile, listDirectory, readLinesFile,
    writeFile)
get_pkgname_by_filename = templateFunction.get_pkgname_by_filename

class ArgumentParserCache(argparse.ArgumentParser):
    def __init__(self):
        super(ArgumentParserCache, self).__init__(
            description="Recalculate package cache")
        self.add_argument('--force', action="store_true",
            help='skip check mtime')

class Cache(object):
    reMerge = re.compile("(merge)\(([-\w/]*)(?:\[[^\]]\])?\)[-!=<>]")
    rePatch = re.compile("^#\s*Calculate.*ac_install_patch==on")

    PATCH_TYPE = "patch"
    MERGE_TYPE = "merge"
    DIRECTORY_TEMPLATE = ".calculate_directory"
    CLT_SUFFIX = ".clt"

    def __init__(self, patch=None, merge=None):
        dv = DataVars()
        dv.importData()
        dv.flIniFile()
        self.dv = dv
        self.base_dn = "/var/lib/calculate/calculate-core/cache"
        self.fn_mtime = path.join(self.base_dn, "merge.mtime")
        self.fn_patch = path.join(self.base_dn, "merge-patch.list")
        self.fn_setup = path.join(self.base_dn, "merge-setup.list")


    def search_merge(self,dn):
        """
        Сканировать директорию с шаблонами
        """
        patch_dirs = []
        for root, dirs, files in os.walk(dn):
            for fn in (path.join(root,x) for x in files):
                data = readFile(fn)
                if self.rePatch.search(data):
                    if path.basename(fn) == self.DIRECTORY_TEMPLATE:
                        patch_dirs.append(path.dirname(fn))
                    patch_template = True
                else:
                    if any(fn.startswith(x) for x in patch_dirs):
                        patch_template = True
                    else:
                        patch_template = False
                for fname, pkg in self.reMerge.findall(data):
                    pkg = pkg or get_pkgname_by_filename(fn)
                    yield (self.PATCH_TYPE if patch_template
                           else self.MERGE_TYPE, pkg)

    def search_merge_clt(self, dn):
        """
        Сканировать clt шаблоны
        """
        for root, dirs, files in os.walk(dn):
            for fn in (path.join(root,x) for x in files 
                       if x.endswith(self.CLT_SUFFIX)):
                data = readFile(fn)
                for fname, pkg in self.reMerge.findall(data):
                    pkg = pkg or get_pkgname_by_filename(fn)
                    yield (self.MERGE_TYPE, pkg)

    def check_new_that(self, mtime_fn, dirs, fn_filter=None):
        """
        Проверить появились ли новые файлы после последней проверки
        """
        if not path.exists(mtime_fn):
            return True
        check_mtime = os.stat(mtime_fn).st_mtime
        for dn in dirs:
            for root, dirs, files in os.walk(dn):
                for fn in (path.join(root,x) for x in files 
                    if fn_filter is None or fn_filter(x)):
                    if os.stat(fn).st_mtime > check_mtime:
                        return True
        return False

    def update(self, force=False):
        template_path = self.dv.Get('main.cl_template_path')
        if (force or 
            self.check_new_that(self.fn_mtime,['/etc'],
                fn_filter=lambda x:x.endswith(self.CLT_SUFFIX)) or
            self.check_new_that(self.fn_mtime,template_path)):
            all_packages = chain(self.search_merge_clt('/etc'),
                            *[self.search_merge(x) for x in template_path])
            for _type, pkgs in groupby(sorted(all_packages), lambda x:x[0]):
                list_packages = sorted(set(y for x,y in pkgs))
                if _type == self.MERGE_TYPE:
                    with writeFile(self.fn_setup) as f:
                        f.write("\n".join(list_packages))
                if _type == self.PATCH_TYPE:
                    with writeFile(self.fn_patch) as f:
                        f.write("\n".join(list_packages))
                with writeFile(self.fn_mtime) as f:
                    f.write(str(time.time()))

if __name__=='__main__':
    apv = ArgumentParserCache()
    args = apv.parse_args()
    Cache().update(force=args.force)
