#
# Copyright (C) 2010, 2011, 2012, 2013, 2014 Intel, Inc.
#
#    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; version 2 of the License.
#
#    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#

"""Handle XML data with Python objects"""

import os,sys
from common.utils import xml_to_obj
from common.git import clone_gitproject
from xml.sax import SAXException

class MappingError(Exception):
    """Local exception class."""
    pass

class Mapping(object):
    """A class to handle mapping xml file """
    def __init__(self, mapping_file):
        """ convert xml to git obj """
        if not os.path.isfile(mapping_file):
            raise MappingError("Fatal: %s is not a regular file" \
                               % mapping_file)

        with open(mapping_file) as filei:
            try:
                self.mapping_obj = xml_to_obj(''.join(filei.readlines()))
            except SAXException, err:
                raise MappingError("Fatal: parsing of xml file %s failed: %s" \
                                   % (mapping_file, str(err)))
    @staticmethod
    def __encode_to_ascii(source):
        """ encode unicode list element to ascii """
        if type(source) == list:
            dest = []
            for i in source:
                if type(i) == unicode:
                    dest.append(i.encode('ascii'))
                else:
                    dest.append(i)
        elif type(source) == dict:
            dest = {}
            for i in source:
                if type(source[i]) == unicode:
                    dest[i] = source[i].encode('ascii')
                else:
                    dest[i] = source[i]
        return dest

    def __get_mapping(self, obj, branch=None):
        """ get OBS target project under one object(project or path) """

        mapping = []
        key_list = ['OBS_project', 'OBS_staging_project', 'OBS_package', 'OBS_use_specname']

        if obj.submission == 'N':
            # <project name="public/tools/ext4-utils" submission="N"/>
            return None
        elif branch:
            for brch in obj.branch:
                # Search the match branch
                if brch.name == branch:
                    if brch.submission == 'N':
                        return None
                    else:
                        brch_dict = {}
                        for key in key_list:
                            brch_dict[key] = brch.__getitem__(key)
                        mapping.append(self.__encode_to_ascii(brch_dict))
        else:
            for brch in obj.branch:
                if brch.submission == 'N':
                    continue
                else:
                    brch_dict = {'Branch_name': brch.__getitem__('name')}
                    for key in key_list:
                        brch_dict[key] = brch.__getitem__(key) or None
                    mapping.append(self.__encode_to_ascii(brch_dict))

        return mapping

    def get_submit_mapping(self, project, branch=None):
        """Get submit mapping though project or branch"""
        if self.mapping_obj.project:
            for prj in self.mapping_obj.project:
                if prj.name == project or prj.name == '/' + project:
                    # submission is blocked, return empty list, otherwise
                    # return matched OBS target project
                    return self.__get_mapping(prj, branch) or []
        if self.mapping_obj.default:
            # Search in path list
            prj_path = project
            while True:
                # parent directory of project/directory
                prj_path = os.path.dirname(os.path.join('/', prj_path))

                if prj_path == '/':
                    for path in self.mapping_obj.default.path:
                        if path.name == '/':
                            return self.__get_mapping(path, branch) or []
                    # No branch match, return NULL
                    return []

                # Search path
                for path in self.mapping_obj.default.path:
                    # path match
                    if os.path.dirname(os.path.join('/', path.name) +
                                       '/') == prj_path:
                        # path pattern match projects blocked
                        #   <path name="/test/" submission="N"/>
                        #   <path name="/apps/">
                        #      <branch name="master" submission="N"/>
                        #   </path>
                        return self.__get_mapping(path, branch) or []
        return []

    def get_virtual_branch_mapping(self):
        """ Get virtual branch mapping """
     
        mapping = []
        if self.mapping_obj.default.virtual:
            brch_dict = {}
            for branch in self.mapping_obj.default.virtual.branch:
                brch_dict[branch.name] = branch.real
            mapping.append(self.__encode_to_ascii(brch_dict))
        return mapping

class MappingV2(object):
    """A class to handle mapping xml file """
    def __init__(self, mapping_file):
        """ convert xml to git obj """
        if not os.path.isfile(mapping_file):
            raise MappingError("Fatal: %s is not a regular file" \
                               % mapping_file)

        with open(mapping_file) as filei:
            try:
                self.mapping_obj = xml_to_obj(''.join(filei.readlines()))
            except SAXException, err:
                raise MappingError("Fatal: parsing of xml file %s failed: %s" \
                                   % (mapping_file, str(err)))
    @staticmethod
    def __encode_to_ascii(source):
        """ encode unicode list element to ascii """
        if type(source) == list:
            dest = []
            for i in source:
                if type(i) == unicode:
                    dest.append(i.encode('ascii'))
                else:
                    dest.append(i)
        elif type(source) == dict:
            dest = {}
            for i in source:
                if type(source[i]) == unicode:
                    dest[i] = source[i].encode('ascii')
                else:
                    dest[i] = source[i]
        return dest

    def get_submit_mapping(self, project, branch=None):
        """Get submit mapping though project or branch"""
        mapping = []

        if self.mapping_obj.configure:
            # if configure.enable is false then return []
            if self.mapping_obj.configure.enable == 'false':
                config_enabled = False

        if self.mapping_obj.branch:
            for brch in self.mapping_obj.branch:
                for prj in brch.project:
                    if (prj.name == project or prj.name == '/' + project) and ( branch in brch.name.split(",") ):
                        item = {}
                        item['OBS_project'] = brch.OBS_project
                        item['OBS_staging_project'] = brch.OBS_staging_project
                        item['OBS_package'] = prj.OBS_package
                        item['OBS_use_specname'] = self.mapping_obj.configure.OBS_use_specname
                        item = self.__encode_to_ascii(item)
                        item['config'] = self.mapping_obj.configure
                        mapping.append(item)

        return mapping

    def get_mapping_list(self, obs_project, staging_project, ignore_config=False):
        """Get all list"""
        mapping = []

        if ignore_config == False and self.mapping_obj.configure:
            # if configure.enable is false then return []
            if self.mapping_obj.configure.enable == 'false':
                return mapping

        if self.mapping_obj.branch:
            for brch in self.mapping_obj.branch:
                if (obs_project and brch.OBS_project != obs_project) or (staging_project and brch.OBS_staging_project != staging_project):
                    continue
                for prj in brch.project:
                    item = {}
                    item['Project_name'] = prj.name
                    item['Branch_name'] = brch.name
                    item['OBS_project'] = brch.OBS_project
                    item['OBS_staging_project'] = brch.OBS_staging_project
                    item['OBS_package'] = prj.OBS_package
                    item = self.__encode_to_ascii(item)
                    mapping.append(item)
            return mapping

        return mapping

    def get_sync_mapping_list(self, project):
        """Get Sync mapping list"""
        mapping = []

        if self.mapping_obj.configure:
            # if configure.enable is false then return []
            if self.mapping_obj.configure.enable == 'false':
                return mapping

        if self.mapping_obj.sync:
            for prjs in self.mapping_obj.sync:
                if (project and prjs.name != project):
                    continue
                for prj in prjs.project:
                    mapping.append(prj.name)
            return mapping

        return mapping

def remove_overlaps(orig_list):
    """docstring for make_unique"""
    result = []
    [result.append(obj) for obj in orig_list if obj not in result]
    # Delete duplicate items for the abs and prerelease
    # Delete prerelease items. Choice the abs item.
    prjs = {}
    for obj in result:
        if obj.get('OBS_project') not in prjs:
            prjs[obj.get('OBS_project')] = [ prj for prj in result \
                                          if prj.get('OBS_project') == obj.get('OBS_project') ]

    #TODO: Remove duplicated items caused by both MappingV1 and MappingV2.
    for p in prjs:
        _stg_ref = {}
        for t in prjs[p]:
            # Listing by staging name
            _stg_name = t.get('OBS_staging_project')
            if _stg_name not in _stg_ref:
                _stg_ref[_stg_name] = t
            else:
                if _stg_ref[_stg_name].get('OBS_package', '') == None \
                    and t.get('OBS_package', '') != None:
                    print '  Removing(1) %s' % _stg_ref[_stg_name]
                    prjs[p].remove(_stg_ref[_stg_name])
                    _stg_ref[_stg_name] = t
                elif len(_stg_ref[_stg_name]) < len(t):
                    print '  Removing(2) %s' % _stg_ref[_stg_name]
                    prjs[p].remove(_stg_ref[_stg_name])
                    _stg_ref[_stg_name] = t
                else:
                    print '  Removing(3) %s' % t
                    prjs[p].remove(t)

    for obj in prjs:
        prerelease_index = abs_index = -1
        i = 0
        for prj in prjs[obj]:
            if 'prerelease' == prj.get('OBS_staging_project'):
                prerelease_index = i
            if 'abs' ==  prj.get('OBS_staging_project'):
                abs_index = i
            i += 1
        if prerelease_index != -1 and abs_index != -1:
            prjs[obj].pop(prerelease_index)

    result = []
    [ result.append(prj) for obj in prjs for prj in prjs[obj] ]

    return result

def get_xml_file_list(path):
    """ get list of xml files """
    file_list = []
    for root, dirs, files in os.walk(path):
        for file in files:
            if file.endswith('.xml'):
                file_list.append(os.path.join(path, file))
    return file_list

def git_obs_map_dryrun(local_dir):

    mapping_prj = os.getenv("MAPPING_PRJ")

    git_obs_mapping_path = local_dir

    try:
        mapping_path_v1 = '{0}/git-obs-mapping.xml'.format(git_obs_mapping_path)
        # get mappings v1
        mymapping = Mapping(mapping_path_v1)
        obs_prjs = mymapping.get_submit_mapping('/', None)
        # get v2 mapping files list
        mapping_path_v2 = '{0}/profiles/'.format(git_obs_mapping_path)
        mapping_v2_file_lists = get_xml_file_list(mapping_path_v2)
        # get mappings v2
        for file in mapping_v2_file_lists:
            mymapping_v2 = MappingV2(file)
            obs_prjs.extend(mymapping_v2.get_submit_mapping('/', None))
        # remove overlapped items
        obs_prjs = remove_overlaps(obs_prjs)
        for obs_prj in obs_prjs:
            print '    %s' % [ obs_prj[x] for x in obs_prj if x is not 'config' ]
    except Exception as err:
        return '\n\n%s' % repr(err)

    return ''

def git_obs_map(gerrit_prj, gerrit_branch=None, gitcache=None, \
                gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None):
    """
    Find an OBS project[s correspondent to Gerrit project and branch
    by parsing git-obs-mapping.xml.
    """

    if gitcache:
        git_cache = gitcache
    else:
        git_cache = os.getenv("GIT_CACHE_DIR")

    mapping_prj = os.getenv("MAPPING_PRJ")

    git_obs_mapping_path = os.path.join(git_cache, mapping_prj)
    mapping_path_v1 = '{0}/git-obs-mapping.xml'.format(git_obs_mapping_path)

    if not os.path.isfile(mapping_path_v1):
        print 'Cloning %s' % mapping_prj
        if not clone_gitproject(mapping_prj, \
                os.path.join(git_cache, mapping_prj), \
                gerrit_hostname=gerrit_hostname, gerrit_username=gerrit_username, gerrit_sshport=gerrit_sshport):
            raise MappingError('Error cloning %s' % mapping_prj)

    # get mappings v1
    mymapping = Mapping(mapping_path_v1)
    obs_prjs = mymapping.get_submit_mapping(gerrit_prj, gerrit_branch)

    # get v2 mapping files list
    mapping_path_v2 = '{0}/profiles/'.format(git_obs_mapping_path)
    mapping_v2_file_lists = get_xml_file_list(mapping_path_v2)

    # get mappings v2
    for file in mapping_v2_file_lists:
        mymapping_v2 = MappingV2(file)
        obs_prjs.extend(mymapping_v2.get_submit_mapping(gerrit_prj,
                                                        gerrit_branch))

    # remove overlapped items
    obs_prjs = remove_overlaps(obs_prjs)

    print 'Found git-obs-mapping: %s ->' % gerrit_prj
    for obs_prj in obs_prjs:
        print '    %s' % [ obs_prj[x] for x in obs_prj if x is not 'config' ]
    return obs_prjs

def git_virtual_branch_map( gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None):
    """
    Find an virtual branch by parsing git-obs-mapping.xml
    """

    git_cache = os.getenv("GIT_CACHE_DIR")
    mapping_prj = os.getenv("MAPPING_PRJ")

    git_obs_mapping_path = os.path.join(git_cache, mapping_prj)
    mapping_path_v1 = '{0}/git-obs-mapping.xml'.format(git_obs_mapping_path)

    if not os.path.isfile(mapping_path_v1):
        print 'Cloning %s' % mapping_prj
        if not clone_gitproject(mapping_prj, \
                os.path.join(git_cache, mapping_prj), \
                gerrit_hostname=gerrit_hostname, gerrit_username=gerrit_username, gerrit_sshport=gerrit_sshport):
            raise MappingError('Error cloning %s' % mapping_prj)

    # get mappings v1
    mymapping = Mapping(mapping_path_v1)
    branchs = mymapping.get_virtual_branch_mapping()

    return branchs

def git_obs_map_full_list(obs_project=None, staging_project=None, gitcache=None, \
                gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None):
    """
    Find an OBS project[s correspondent to Gerrit project and branch
    by parsing git-obs-mapping.xml.
    """

    if gitcache:
        git_cache = gitcache
    else:
        git_cache = os.getenv("GIT_CACHE_DIR")

    mapping_prj = os.getenv("MAPPING_PRJ")

    git_obs_mapping_path = os.path.join(git_cache, mapping_prj)
    mapping_path_v1 = '{0}/git-obs-mapping.xml'.format(git_obs_mapping_path)

    if not os.path.isfile(mapping_path_v1):
        print 'Cloning %s' % mapping_prj
        if not clone_gitproject(mapping_prj, \
                os.path.join(git_cache, mapping_prj), \
                gerrit_hostname=gerrit_hostname, gerrit_username=gerrit_username, gerrit_sshport=gerrit_sshport):
            raise MappingError('Error cloning %s' % mapping_prj)

    obs_prjs = []

    # get mappings v1
    mymapping = Mapping(mapping_path_v1)
    obs_prjs = mymapping.get_submit_mapping('/', None)

    # get v2 mapping files list
    mapping_path_v2 = '{0}/profiles/'.format(git_obs_mapping_path)
    mapping_v2_file_lists = get_xml_file_list(mapping_path_v2)

    # get mappings v2
    for file in mapping_v2_file_lists:
        mymapping_v2 = MappingV2(file)
        obs_prjs.extend(mymapping_v2.get_mapping_list(obs_project, staging_project, ignore_config=True))

    # remove overlapped items
    #obs_prjs = remove_overlaps(obs_prjs)

    return obs_prjs

def get_ref_map(gerrit_prj, gerrit_branch=None, gitcache=None, \
                gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None):
    """
    Find an OBS project[s correspondent to Gerrit project and branch
    by parsing git-obs-mapping.xml.
    """

    if gitcache:
        git_cache = gitcache
    else:
        git_cache = os.getenv("GIT_CACHE_DIR")

    mapping_prj = os.getenv("REF_MAPPING_PRJ")

    if not clone_gitproject(mapping_prj, \
            os.path.join(git_cache, mapping_prj), \
            gerrit_hostname=gerrit_hostname, gerrit_username=gerrit_username, gerrit_sshport=gerrit_sshport):
        raise MappingError('Error cloning %s' % mapping_prj)

    git_ref_mapping_path = os.path.join(git_cache, mapping_prj)
    mapping_path = '{0}/git-ref-mapping.xml'.format(git_ref_mapping_path)

    if not os.path.isfile(mapping_path):
        raise MappingError('mapping file not found %s' % mapping_prj)

    # get mappings
    mymapping = Mapping(mapping_path)
    obs_prjs = mymapping.get_submit_mapping(gerrit_prj, gerrit_branch)

    return obs_prjs

def get_sync_map_list(obs_project=None, gitcache=None, \
                gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None):
    """
    Find an sync project in git-obs-mapping
    """
    

    if gitcache:
        git_cache = gitcache
    else:
        git_cache = os.getenv("GIT_CACHE_DIR")

    mapping_prj = os.getenv("MAPPING_PRJ")

    git_obs_mapping_path = os.path.join(git_cache, mapping_prj)

    if not clone_gitproject(mapping_prj, \
            os.path.join(git_cache, mapping_prj), \
            gerrit_hostname=gerrit_hostname, gerrit_username=gerrit_username, gerrit_sshport=gerrit_sshport):
        raise MappingError('Error cloning %s' % mapping_prj)

    sync_prjs = []

    # get sync mapping files list
    mapping_path = '{0}/sync/'.format(git_obs_mapping_path)
    mapping_file_lists = get_xml_file_list(mapping_path)

    # get mappings
    for file in mapping_file_lists:
        mymapping = MappingV2(file)
        sync_prjs.extend(mymapping.get_sync_mapping_list(obs_project))

    return sync_prjs

