# Software License Agreement (BSD License)
#
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following
#    disclaimer in the documentation and/or other materials provided
#    with the distribution.
#  * Neither the name of Open Source Robotics Foundation, Inc. nor
#    the names of its contributors may be used to endorse or promote
#    products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import print_function

import difflib
import sys

import yaml

from . import get_distribution_file, get_distribution_files, get_doc_build_files, get_doc_file, get_index, get_release_build_files, get_release_file, get_source_build_files, get_source_file
from .loader import load_url


def verify_files_parsable(index_url):
    return verify_files(index_url, _check_files_parsable, include_deprecated=False)


def _check_files_parsable(index, dist_name, loader_function, _yaml_url, _file_type):
    try:
        loader_function(index, dist_name)
    except Exception as e:
        print(str(e), file=sys.stderr)
        return False
    return True


def reformat_files(index_url):
    return verify_files(index_url, _reformat_files)


def verify_files_identical(index_url):
    return verify_files(index_url, _check_files_identical)


def verify_files(index_url, callback, include_deprecated=False):
    identical = True
    index = get_index(index_url)
    for dist_name in sorted(index.distributions.keys()):
        dist = index.distributions[dist_name]
        if index.version < 3:
            providers = [get_distribution_file]
        else:
            providers = [get_distribution_files]
        if include_deprecated:
            providers.extend([get_release_file, get_source_file, get_doc_file])
        file_providers = {
            'distribution': (providers, 'distribution'),
        }
        if index.version < 3:
            file_providers.update({
                'release_builds': (get_release_build_files, 'release-build'),
                'source_builds': (get_source_build_files, 'source-build'),
                'doc_builds': (get_doc_build_files, 'doc-build')
            })
        for key in sorted(file_providers.keys()):
            provider, file_type = file_providers[key]
            yaml_url = dist[key]
            if isinstance(provider, list):
                for p in provider:
                    identical &= callback(index, dist_name, p, yaml_url, file_type)
            else:
                identical &= callback(index, dist_name, provider, yaml_url, file_type)

    return identical


def _reformat_files(index, dist_name, loader_function, yaml_url, file_type):
    all_identical = True
    files = loader_function(index, dist_name)
    if not isinstance(files, list):
        files = [files]
        yaml_url = [yaml_url]
    for i, f in enumerate(files):
        url = yaml_url[i]
        if not url.startswith('file://'):
            print('Skipping non-file url: %s' % url)
            continue
        identical = _check_file_identical(f, yaml_url[i], file_type)
        all_identical &= identical
        path = url[7:]
        if identical:
            print('Skipping identical file: %s' % path)
            continue
        print('Updating file: %s' % path)
        data = f.get_data()
        dist_file_data = _to_yaml(data)
        dist_file_data = '\n'.join(_yaml_header_lines(file_type, data['version'])) + '\n' + dist_file_data
        with open(path, 'w') as f:
            f.write(dist_file_data)
    return all_identical


def _check_files_identical(index, dist_name, loader_function, yaml_url, file_type):
    identical = True
    files = loader_function(index, dist_name)
    if not isinstance(files, list):
        files = [files]
        yaml_url = [yaml_url]
    for i, f in enumerate(files):
        identical &= _check_file_identical(f, yaml_url[i], file_type)
    return identical


def _check_file_identical(dist_file, yaml_url, file_type):
    yaml_str = load_url(yaml_url)
    yaml_lines = yaml_str.splitlines()
    dist_file_data = dist_file.get_data()
    dist_file_lines = _to_yaml(dist_file_data).splitlines()
    dist_file_lines[0:0] = _yaml_header_lines(file_type, dist_file_data['version'])

    if yaml_lines != dist_file_lines:
        diff = difflib.unified_diff(
            yaml_lines, dist_file_lines,
            yaml_url, 'loaded-and-saved',
            n=1, lineterm='')
        for line in diff:
            print(line, file=sys.stderr)
        return False
    return True


def _to_yaml(data):
    yaml_str = yaml.dump(data, default_flow_style=False)
    yaml_str = yaml_str.replace(': null', ':')
    yaml_str = yaml_str.replace(': {}', ':')
    return yaml_str


def _yaml_header_lines(file_type, version):
    rep = '141'
    if file_type == 'index':
        if version == 3:
            rep = '143'
        elif version == 4:
            rep = '153'
    if file_type == 'distribution' and version == 2:
        rep = '143'
    return [
        '%YAML 1.1',
        '# ROS %s file' % file_type,
        '# see REP %s: http://ros.org/reps/rep-0%s.html' % (rep, rep),
        '---'
    ]
