#!/usr/bin/python -tt
# vim: ai ts=4 sts=4 et sw=4
#
# 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.
#

"""Tool collections can be used everywhere"""

import os
import glob
import subprocess
import re
import shutil
import xml.etree.cElementTree as ET
import requests
from bs4 import BeautifulSoup
from urllib2 import urlopen, ProxyHandler, build_opener, install_opener, URLError, HTTPError

from common import runner

class RuntimeException(Exception):
    """Local error handler"""
    pass

class UtilsError(Exception):
    """Local error handler"""
    pass

class Workdir(object):
    """A class which supports with statement"""

    def __init__(self, path):
        self._newdir = path
        self._cwd = os.getcwd()

    def __enter__(self):
        if not os.path.exists(self._newdir):
            os.makedirs(self._newdir)
        os.chdir(self._newdir)

    def __exit__(self, _type, _value, _tb):
        os.chdir(self._cwd)

# Retry decorator
def retry(retries=3):
    """This will try to execute one function 3 times(by default) until \
            success."""
    def deco_retry(func):
        """Function wrapper"""
        def f_retry(*args):
            """Retry Loop"""
            retry_count = retries
            retval = func(*args)
            while retry_count > 0:
                if retval == True:
                    return True
                else:
                    print '%s failed, retrying...' % func.func_name
                    retry_count -= 1
            return False
        return f_retry
    return deco_retry

def find_spec(workdir):
    """Return specfile list in workdir"""

    return glob.glob('%s/*.spec' % workdir)

def find_changelog(workdir):
    """Get change log needed"""

    changes = glob.glob('%s/*.changes' % workdir)
    if len(changes) > 1:
        print "Can't decide which changelog file to use."
    elif len(changes) == 1:
        return changes[0]

    return None

def parse_link(path):
    """Parse path link"""

    if os.path.islink(path):
        return runner.outs('readlink %s' % path)
    else:
        return os.path.basename(path)

def unicode_to_str(obj):
    """convert unicode object to str"""

    if isinstance(obj, list):
        return [unicode_to_str(element) for element in obj]
    elif isinstance(obj, dict):
        return {unicode_to_str(key) : unicode_to_str(value) for key, value \
                in obj.iteritems()}
    elif isinstance(obj, unicode):
        return obj.encode('utf-8')
    else:
        return obj

def hardlink(src, dst):
    print 'src = %s , dst = %s ' %(src, dst)
    working_dir = os.getcwd()
    if not os.path.exists(dst):
        os.makedirs(dst)
    os.chdir(src)
    for root, dirs, files in os.walk('.'):
        curdst = os.path.join(dst, root)
        for d in dirs:
            os.mkdir(os.path.join(curdst, d))
        for f in files:
            fromfile = os.path.join(root, f)
            to = os.path.join(curdst, f)
            os.link(fromfile, to)
    os.chdir(working_dir)

def sync(source, destination, remove=True, hardlinks=False):
    """ sync srouce to destination, support local filesystem,
        ssh and rsync protocol.

        Note: access to destination server must be use key authentication
              or anonymous
    """
    ret_code = -1

    # Through rsync protocol
    if destination.startswith('rsync:'):

        # Cut trailing slash due to error (attempt to hack rsync failed)
        if os.path.isdir(source):
            if source.endswith("/"):
                source = source[:-1]
            source = "%s/*" % source

        cmd = "rsync -av --delay-updates %s %s" % (source, destination)
        print cmd

        try:
            ret_code = subprocess.call(cmd, shell=True)
        except OSError as err:
            raise RuntimeException("Execution of %s failed: %s" %
                                   (cmd, str(err)))

    # Through ssh protocol
    elif destination.startswith('ssh:'):
        destination = destination.replace("ssh://", "")
        cmd = "scp -r %s/* %s" % (source, destination)

        try:
            ret_code = subprocess.call(cmd, shell=True)
        except OSError as err:
            raise RuntimeException("Execution of %s failed: %s" %
                                   (cmd, str(err)))

    # Try to take the destination as local path
    else:
        if remove:
            shutil.move(source, destination)
        else:
            if hardlinks:
                # hardlink
                hardlink(source, destination)
            else:
                shutil.copytree(source, destination)
        # No exception so far, return code 0
        ret_code = 0

    return ret_code

def sync_get(source, destination, remove=True):

    ret_code = -1

    if source.startswith('rsync:'):
        cmd = "rsync -caz %s %s 2> /dev/null" % (source, destination)
        try:
            ret_code = subprocess.call(cmd, shell=True)
        except OSError as err:
            raise RuntimeException("Execution of %s failed: %s" %
                                   (cmd, str(err)))

    return ret_code

def set_permissions(tpath, modes=(0o644, 0o755)):
    """
    Recursively set permission bits for files and directories.
    Parameters:
        tpath: top directory path
        modes: sequence of two modes: (mode_for_files, mode_for_directories)
    """
    for root, dirs, files in os.walk(tpath):
        for fname in files + dirs:
            path = os.path.join(root, fname)
            os.chmod(path, os.stat(path).st_mode | modes[fname in dirs])

def xml_to_obj(xml_f):
    """
    A simple function to converts XML data
    Xml data:
    <?xml version="1.0" ?>
    <build id="1.0">
      <id>test</id>
      <archs name='atom'>
        <arch name='lz'>ia32</arch>
        <arch>ai32</arch>
        <arch>ai32</arch>
      </archs>
      <repo>repos/atom/packages</repo>
      <repo type="source"></repo>
      <repo type="debug">repos/atom/debug</repo>
    </build>
    Change result:
    {archs:{arch:[{name:u'lz', data:u'ia32'}, u'ai32', u'ai32'], name:u'atom'},
            id:[u'1.0', u'test'],
            repo:[u'repos/atom/packages', {type:u'source'},
                  {type:u'debug', data:u'repos/atom/debug'}]}
    stack = [(node, node.children, [node.children.data]),
            (node, node.children, [])....]
    """
    node_char = re.compile('[^_0-9a-zA-Z]')
    def _name_regular(name):
        """Split name though regular"""
        return node_char.sub('_', name)

    class XMLNode(object):
        """Wrap XML attributes and child elements"""
        def __init__(self):
            self._attrs = {}    # XML attributes and child elements
            self.data = None    # text data
        def add_attr(self, name, value):
            """Add XML attribute"""
            if name in self._attrs:
                # multiple attribute of the same name are represented by a list
                children = self._attrs[name]
                if not isinstance(children, list):
                    self._attrs[name] = [children, value]
                else:
                    children.append(value)
            else:
                self._attrs[name] = value
        def __getitem__(self, key):
            if isinstance(key, basestring):
                return self._attrs.get(key, None)
            else:
                return [self][key]
        def __nonzero__(self):
            return bool(self._attrs or self.data)
        def __getattr__(self, name):
            if name.startswith('__'):
                # need to do this for Python special methods???
                raise AttributeError(name)
            return self._attrs.get(name, None)
    #Get tree root element
    if isinstance(xml_f, basestring):
        root = ET.fromstring(xml_f)
    else:
        root = ET.parse(xml_f).getroot()
    stack = []
    stack.append((root, root.getchildren(), []))

    tag = None
    xml_node = None
    # convert xml data from sub nodes start
    while stack:
        cur_node, child_nodes, data_list = stack[-1]

        if tag and xml_node:
            data_list.append((tag, xml_node))
            tag = None
            xml_node = None

        if child_nodes:
            child_node = child_nodes[0]
            child_nodes.remove(child_node)
            stack.append((child_node, child_node.getchildren(), []))
            continue

        xml_node = XMLNode()

        # add current node attrib into XMLNode
        for key in cur_node.attrib:
            xml_node.add_attr(_name_regular(key), cur_node.attrib[key])

        # Add previous node attrib into XMLNode
        for (previous_tag, node) in data_list:
            xml_node.add_attr(_name_regular(previous_tag), node)

        # Add current node text info XMLNode's data
        if cur_node.text and ''.join(cur_node.text.strip()):
            if xml_node:
                xml_node.data = cur_node.text
            else:
                xml_node = cur_node.text
        tag = cur_node.tag
        # remove namespaces of node.tag
        # pylint: disable=W0612
        # disable unused variable
        if tag[0] == '{':
            url, tag = tag[1:].split('}')
        stack.pop()
        # pylint: enable=W0612
        if not stack:
            return xml_node

def make_latest_link(snapshot_path):
    """ make the latest repo link to the new repo
        snapshot_path (str): the local path to snapshot
    """
    latest = os.path.join(snapshot_path, "../latest")
    rel_path = os.path.basename(snapshot_path)
    if os.path.lexists(latest):
        os.unlink(latest)
    os.symlink(rel_path, latest)

def make_base_link(snapshot_path, base_snapshot_path):
    """ make the base repo link to the new repo
        snapshot_path (str): the local path to snapshot
        base_snapshot_path (str): the base path to snapshot
    """
    repos_path = os.path.join(snapshot_path, "repos")
    base_path = os.path.join(repos_path, "BASE")

    rel_path = os.path.join("../../..", base_snapshot_path)

    if os.path.lexists(base_path):
        os.unlink(base_path)
    os.symlink(rel_path, base_path)

def execute_shell(cmd, progress=False):
    print "[INFO] command : %s" % cmd
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if progress:
        line_iterator = iter(proc.stdout.readline, b"")
        for line in line_iterator:
            print "    %s" % line[:-1]
    out, err = proc.communicate()
    if cmd.startswith("rsync"):
        if err:
            print "stderr: %s" % err
            return 'err'

    if err:
        print "stderr: %s" % err
        return None

    o = out.strip().split('\n')
    print "o: %s" % o
    if len(o) == 1:
        if o[0] == '':
            return None
    return o

def tail(f, n=30, c=2048, offset=0):
    cmd = "tail %s -c %d" % (f, c)
    return runner.outs(cmd)

def list_files_in_url(url, ext=''):
    page = requests.get(url).text
    soup = BeautifulSoup(page, 'html.parser')
    return [url + '/' + node.get('href') for node in soup.find_all('a') if node.get('href').endswith(ext)]

def wget_noproxy(filefromurl, filesaveto):
    try:
        proxy_handler = ProxyHandler({})
        opener = build_opener(proxy_handler)
        install_opener(opener)
        f = urlopen(filefromurl)
        with open(filesaveto, "wb") as local_file:
            local_file.write(f.read())
    except HTTPError, e:
        print "HTTP Error: %s %s", e.code, filefromurl
        pass
    except URLError, e:
        print "URL Error: %s %s", e.reason, filefromurl
        pass

def grap_text_from_url(url):
    html = urlopen(url).read()
    soup = BeautifulSoup(html)

    # rip all script and style elements out
    for script in soup(["script", "style"]):
        script.extract()

    text = soup.get_text()
    lines = (line.strip() for line in text.splitlines())
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    text = '\n'.join(chunk for chunk in chunks if chunk)

    return text

