# -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111

import datetime
import hashlib
import os
import re
import shutil
from six.moves.urllib.parse import unquote
import zipfile
import fnmatch

import werkzeug
from slapos.runner.utils import realpath, tail, isText
from slapos.util import str2bytes


class FileBrowser(object):
  """This class contains all base functions for file browser"""

  def __init__(self, config):
    self.config = config

  def _realdir(self, dir):
    realdir = realpath(self.config, unquote(dir))
    if not realdir:
      raise NameError('Could not load directory %s: Permission denied' % dir)
    return realdir

  def _filterPathList(self, path_list):
    """Filter out paths that matches a list ignored file patterns.
    """
    # This could be configurable ...
    ignored_file_list = '''
*.py[cod]
*.o
'''
    for pattern in ignored_file_list.splitlines():
      if pattern.strip():
        path_list = [path for path in path_list if not fnmatch.fnmatch(path, pattern)]
    return path_list


  def listDirs(self, dir, all=False):
    """List elements of directory 'dir' taken"""
    html = 'var gsdirs = [], gsfiles = [];'

    dir = unquote(dir)
    # XXX-Marco 'dir' and 'all' should not shadow builtin names
    realdir = realpath(self.config, dir)
    if not realdir:
      raise NameError('Could not load directory %s: Permission denied' % dir)

    ldir = sorted(os.listdir(realdir), key=str.lower)
    for f in self._filterPathList(ldir):
      if f.startswith('.') and not all:  # do not display this file/folder
        continue
      ff = os.path.join(dir, f)
      realfile = os.path.join(realdir, f)
      mdate = datetime.datetime.fromtimestamp(os.path.getmtime(realfile)
                                              ).strftime("%Y-%d-%m %I:%M")
      md5sum = hashlib.md5(str2bytes(realfile)).hexdigest()
      if not os.path.isdir(realfile):
        size = os.path.getsize(realfile)
        regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
        ext = regex.sub(r'\2', f)
        if ext == f:
          ext = "unknow"
        else:
          ext = str.lower(ext)
        html += 'gsfiles.push(new gsItem("1", "%s", "%s", "%s", "%s", "%s", "%s"));' % (f, ff, size, md5sum, ext, mdate)
      else:
        html += 'gsdirs.push(new gsItem("2", "%s", "%s", "0", "%s", "dir", "%s"));' % (f, ff, md5sum, mdate)
    return html

  def fancylistDirs(self, dir, key, listfiles, all=False):
    dir = unquote(dir)
    realdir = realpath(self.config, dir)
    if not realdir:
      raise NameError('Could not load directory %s: Permission denied' % dir)
    fileList = []
    dirList = []
    i = 0
    ldir = sorted(os.listdir(realdir), key=str.lower)
    for f in self._filterPathList(ldir):
      if f.startswith('.') and not all:  # do not display this file/folder
        continue
      ff = os.path.join(dir, f)
      realfile = os.path.join(realdir, f)
      identity = "%s%s" % (key, i)
      if os.path.isdir(realfile):
        dirList.append({"title": f, "key": identity,
                        "folder":True, "lazy":True, "path": ff})
      elif listfiles:
        regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
        ext = regex.sub(r'\2', f)
        if ext == f:
          ext = ""
        fileList.append({"title": f, "key": identity,
                        "extraClasses": "ext_"+ext, "path": ff})
      i+=1
    return (dirList + fileList)

  def makeDirectory(self, dir, filename):
    """Create a directory"""
    realdir = self._realdir(dir)
    folder = os.path.join(realdir, filename)
    if not os.path.exists(folder):
      os.mkdir(folder, 0o744)
      return "{result: '1'}"
    else:
      return "{result: '0'}"

  def makeFile(self, dir, filename):
    """Create a file in a directory dir taken"""
    realdir = self._realdir(dir)
    fout = os.path.join(realdir, filename)
    if not os.path.exists(fout):
      open(fout, 'w')
      return "var responce = {result: '1'}"
    else:
      return "{result: '0'}"

  def deleteItem(self, dir, files):
    """Delete a list of files or directories"""
    # XXX-Marco do not shadow 'dir'
    realdir = self._realdir(dir)
    lfiles = unquote(files).split(',,,')
    try:
      # XXX-Marco do not shadow 'file'
      for item in lfiles:
        filepath = os.path.join(realdir, item)
        if not item or not os.path.exists(filepath):
          continue  # silent skip file....
        details = filepath.split('/')
        last = details[-1]
        if last and last.startswith('.'):
          continue  # cannot delete this file/directory, to prevent security
        if os.path.isdir(filepath):
          shutil.rmtree(filepath)
        else:
          os.unlink(filepath)
    except Exception as e:
      return str(e)
    return "{result: '1'}"

  def copyItem(self, dir, files, del_source=False):
    """Copy a list of files or directory to dir"""
    realdir = self._realdir(dir)
    lfiles = unquote(files).split(',,,')
    try:
      # XXX-Marco do not shadow 'file'
      for file in lfiles:
        realfile = realpath(self.config, file)
        if not realfile:
          raise NameError('Could not load file or directory %s: Permission denied' % file)
        #prepare destination file
        details = realfile.split('/')
        dest = os.path.join(realdir, details[-1])
        if os.path.exists(dest):
          raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
        if os.path.isdir(realfile):
          shutil.copytree(realfile, dest)
          if del_source:
            shutil.rmtree(realfile)
        else:
          shutil.copy(realfile, dest)
          if del_source:
            os.unlink(realfile)
    except Exception as e:
      return str(e)
    return "{result: '1'}"

  def rename(self, dir, filename, newfilename):
    """Rename file or directory to dir/filename"""
    realdir = self._realdir(dir)
    realfile = realpath(self.config, unquote(filename))
    if not realfile:
      raise NameError('Could not load directory %s: Permission denied' % filename)
    tofile = os.path.join(realdir, newfilename)
    if not os.path.exists(tofile):
      os.rename(realfile, tofile)
      return "{result: '1'}"
    raise NameError('NOT ALLOWED OPERATION : File or directory already exists')

  def copyAsFile(self, dir, filename, newfilename):
    """Copy file or directory to dir/filename"""
    realdir = self._realdir(dir)
    fromfile = os.path.join(realdir, filename)
    tofile = os.path.join(realdir, newfilename)
    if not os.path.exists(fromfile):
      raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
    while os.path.exists(tofile):
      tofile += "_1"
    shutil.copy(fromfile, tofile)
    return "{result: '1'}"

  def uploadFile(self, dir, files):
    """Upload a list of files in directory dir"""
    realdir = self._realdir(dir)
    for file in files:
      if files[file]:
        filename = werkzeug.secure_filename(files[file].filename)
        if not os.path.exists(os.path.join(dir, filename)):
          files[file].save(os.path.join(realdir, filename))
    return "{result: '1'}"

  def downloadFile(self, dir, filename):
    """Download file dir/filename"""
    realdir = self._realdir(dir)
    file = os.path.join(realdir, unquote(filename))
    if not os.path.exists(file):
      raise NameError('NOT ALLOWED OPERATION : File or directory does not exist %s'
                      % os.path.join(dir, filename))
    return file

  def zipFile(self, dir, filename, newfilename):
    """Add filename to archive as newfilename"""
    realdir = self._realdir(dir)
    tozip = os.path.join(realdir, newfilename)
    fromzip = os.path.join(realdir, filename)
    if not os.path.exists(fromzip):
      raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
    if not os.path.exists(tozip):
      zip = zipfile.ZipFile(tozip, 'w', zipfile.ZIP_DEFLATED)
      if os.path.isdir(fromzip):
        rootlen = len(fromzip) + 1
        for base, _, files in os.walk(fromzip):
          for filename in files:
            fn = os.path.join(base, filename).encode("utf-8")
            zip.write(fn, fn[rootlen:])       # XXX can fail if 'fromzip' contains multibyte characters
      else:
        zip.write(fromzip)
      zip.close()
      return "{result: '1'}"
    raise NameError('NOT ALLOWED OPERATION : File or directory already exists')

  def unzipFile(self, dir, filename, newfilename):
    """Extract a zipped archive"""
    realdir = self._realdir(dir)
    target = os.path.join(realdir, newfilename)
    archive = os.path.join(realdir, filename)
    if not os.path.exists(archive):
      raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
    if not os.path.exists(target):
      zip = zipfile.ZipFile(archive)
      #member = zip.namelist()
      zip.extractall(target)
      #if len(member) > 1:
      #  zip.extractall(target)
      #else:
      #  zip.extract(member[0], newfilename)
      return "{result: '1'}"
    raise NameError('NOT ALLOWED OPERATION : File or directory already exists')

  def readFile(self, dir, filename, truncate=False):
    """Read file dir/filename and return content"""
    realfile = realpath(self.config, os.path.join(unquote(dir),
                        unquote(filename)))
    if not realfile:
      raise NameError('Could not load directory %s: Permission denied' % dir)
    if not isText(realfile):
      return "FILE ERROR: Cannot display binary file, please open a text file only!"
    if not truncate:
      return open(realfile).read()
    else:
      return tail(open(realfile), 0)
