""" This is the guy that actually modifies the content of the CS
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import zlib
import difflib
import six

from diraccfg import CFG
from DIRAC.Core.Utilities import List, Time
from DIRAC.ConfigurationSystem.Client.ConfigurationData import gConfigurationData
from DIRAC.Core.Security.ProxyInfo import getProxyInfo

__RCSID__ = "$Id$"


class Modificator(object):

  def __init__(self, rpcClient=False, commiterId="unknown"):
    self.commiterTag = "@@-"
    self.commiterId = commiterId
    self.cfgData = CFG()
    self.rpcClient = None
    if rpcClient:
      self.setRPCClient(rpcClient)

  def loadCredentials(self):
    retVal = getProxyInfo()
    if retVal['OK']:
      credDict = retVal['Value']
      self.commiterId = "%s@%s - %s" % (credDict['username'],
                                        credDict['group'],
                                        Time.dateTime().strftime("%Y-%m-%d %H:%M:%S"))
      return retVal
    return retVal

  def setRPCClient(self, rpcClient):
    self.rpcClient = rpcClient

  def loadFromRemote(self):
    retVal = self.rpcClient.getCompressedData()
    if retVal['OK']:
      self.cfgData = CFG()
      data = retVal['Value']
      if six.PY3 and isinstance(data, str):
        data = data.encode(errors="surrogateescape")
      self.cfgData.loadFromBuffer(zlib.decompress(data).decode())
    return retVal

  def getCFG(self):
    return self.cfgData

  def getSections(self, sectionPath):
    return gConfigurationData.getSectionsFromCFG(sectionPath, self.cfgData)

  def getComment(self, sectionPath):
    return gConfigurationData.getCommentFromCFG(sectionPath, self.cfgData)

  def getOptions(self, sectionPath):
    return gConfigurationData.getOptionsFromCFG(sectionPath, self.cfgData)

  def getOptionsDict(self, sectionPath):
    """Gives the options of a CS section in a Python dict with values as
    lists"""

    opts = self.getOptions(sectionPath)
    pathDict = dict((o, self.getValue("%s/%s" % (sectionPath, o))) for o in opts)
    return pathDict

  def getDictRootedAt(self, relpath="", root=""):
    """Gives the configuration rooted at path in a Python dict. The
    result is a Python dictionary that reflects the structure of the
    config file."""
    def getDictRootedAt(path):
      retval = {}
      opts = self.getOptionsDict(path)
      secs = self.getSections(path)
      for k in opts:
        retval[k] = opts[k]
      for i in secs:
        retval[i] = getDictRootedAt(path + "/" + i)
      return retval

    return getDictRootedAt(root + "/" + relpath)

  def getValue(self, optionPath):
    return gConfigurationData.extractOptionFromCFG(optionPath, self.cfgData)

  def sortAlphabetically(self, path, ascending=True):
    cfg = self.__getParentCFG(path, parentLevel=0)
    if cfg:
      if cfg.sortAlphabetically(ascending):
        self.__setCommiter(path)

  def __getParentCFG(self, path, parentLevel=1):
    sectionList = List.fromChar(path, "/")
    cfg = self.cfgData
    try:
      if parentLevel > 0:
        sectionList = sectionList[:-parentLevel]
      for section in sectionList:
        cfg = cfg[section]
      return cfg
    except Exception:
      return False

  def __setCommiter(self, entryPath, cfg=False):
    if not cfg:
      cfg = self.__getParentCFG(entryPath)
    entry = List.fromChar(entryPath, "/")[-1]
    comment = cfg.getComment(entry)
    filteredComment = [line.strip() for line in comment.split("\n") if line.find(self.commiterTag) != 0]
    filteredComment.append("%s%s" % (self.commiterTag, self.commiterId))
    cfg.setComment(entry, "\n".join(filteredComment))

  def setOptionValue(self, optionPath, value):
    levelList = [level.strip() for level in optionPath.split("/") if level.strip() != ""]
    parentPath = "/%s" % "/".join(levelList[:-1])
    optionName = List.fromChar(optionPath, "/")[-1]
    self.createSection(parentPath)
    cfg = self.__getParentCFG(optionPath)
    if not cfg:
      return
    cfg.setOption(optionName, value)
    self.__setCommiter(optionPath, cfg)

  def createSection(self, sectionPath):
    levelList = [level.strip() for level in sectionPath.split("/") if level.strip() != ""]
    currentPath = ""
    cfg = self.cfgData
    createdSection = False
    for section in levelList:
      currentPath += "/%s" % section
      if section not in cfg.listSections():
        cfg.createNewSection(section)
        self.__setCommiter(currentPath)
        createdSection = True
      cfg = cfg[section]
    return createdSection

  def setComment(self, entryPath, value):
    cfg = self.__getParentCFG(entryPath)
    entry = List.fromChar(entryPath, "/")[-1]
    if cfg.setComment(entry, value):
      self.__setCommiter(entryPath)
      return True
    return False

  def existsSection(self, sectionPath):
    sectionList = List.fromChar(sectionPath, "/")
    cfg = self.cfgData
    try:
      for section in sectionList[:-1]:
        cfg = cfg[section]
      return len(sectionList) == 0 or sectionList[-1] in cfg.listSections()
    except Exception:
      return False

  def existsOption(self, optionPath):
    sectionList = List.fromChar(optionPath, "/")
    cfg = self.cfgData
    try:
      for section in sectionList[:-1]:
        cfg = cfg[section]
      return sectionList[-1] in cfg.listOptions()
    except Exception:
      return False

  def renameKey(self, path, newName):
    parentCfg = self.cfgData.getRecursive(path, -1)
    if not parentCfg:
      return False
    pathList = List.fromChar(path, "/")
    oldName = pathList[-1]
    if parentCfg['value'].renameKey(oldName, newName):
      pathList[-1] = newName
      self.__setCommiter("/%s" % "/".join(pathList))
      return True
    else:
      return False

  def copyKey(self, originalKeyPath, newKey):
    parentCfg = self.cfgData.getRecursive(originalKeyPath, -1)
    if not parentCfg:
      return False
    pathList = List.fromChar(originalKeyPath, "/")
    originalKey = pathList[-1]
    if parentCfg['value'].copyKey(originalKey, newKey):
      self.__setCommiter("/%s/%s" % ("/".join(pathList[:-1]), newKey))
      return True
    return False

  def removeOption(self, optionPath):
    if not self.existsOption(optionPath):
      return False
    cfg = self.__getParentCFG(optionPath)
    optionName = List.fromChar(optionPath, "/")[-1]
    return cfg.deleteKey(optionName)

  def removeSection(self, sectionPath):
    if not self.existsSection(sectionPath):
      return False
    cfg = self.__getParentCFG(sectionPath)
    sectionName = List.fromChar(sectionPath, "/")[-1]
    return cfg.deleteKey(sectionName)

  def loadFromBuffer(self, data):
    self.cfgData = CFG()
    self.cfgData.loadFromBuffer(data)

  def loadFromFile(self, filename):
    self.cfgData = CFG()
    self.mergeFromFile(filename)

  def dumpToFile(self, filename):
    with open(filename, "wt") as fd:
      fd.write(str(self.cfgData))

  def mergeFromFile(self, filename):
    cfg = CFG()
    cfg.loadFromFile(filename)
    self.cfgData = self.cfgData.mergeWith(cfg)

  def mergeFromCFG(self, cfg):
    self.cfgData = self.cfgData.mergeWith(cfg)

  def mergeSectionFromCFG(self, sectionPath, cfg):
    parentDict = self.cfgData.getRecursive(sectionPath, -1)
    parentCFG = parentDict['value']
    secName = [lev.strip() for lev in sectionPath.split("/") if lev.strip()][-1]
    secCFG = parentCFG[secName]
    if not secCFG:
      return False
    mergedCFG = secCFG.mergeWith(cfg)
    parentCFG.deleteKey(secName)
    parentCFG.createNewSection(secName, parentDict['comment'], mergedCFG)
    self.__setCommiter(sectionPath)
    return True

  def __str__(self):
    return str(self.cfgData)

  def commit(self):
    compressedData = zlib.compress(str(self.cfgData).encode(), 9)
    return self.rpcClient.commitNewData(compressedData)

  def getHistory(self, limit=0):
    retVal = self.rpcClient.getCommitHistory(limit)
    if retVal['OK']:
      return retVal['Value']
    return []

  def showCurrentDiff(self):
    retVal = self.rpcClient.getCompressedData()
    if retVal['OK']:
      data = retVal['Value']
      if six.PY3 and isinstance(data, str):
        data = data.encode(errors="surrogateescape")
      remoteData = zlib.decompress(data).decode().splitlines()
      localData = str(self.cfgData).splitlines()
      return difflib.ndiff(remoteData, localData)
    return []

  def getVersionDiff(self, fromDate, toDate):
    retVal = self.rpcClient.getVersionContents([fromDate, toDate])
    if retVal['OK']:
      fromData = retVal['Value'][0]
      if six.PY3 and isinstance(fromData, str):
        fromData = fromData.encode(errors="surrogateescape")
      fromData = zlib.decompress(fromData).decode()

      toData = retVal['Value'][1]
      if six.PY3 and isinstance(toData, str):
        toData = toData.encode(errors="surrogateescape")
      toData = zlib.decompress(toData).decode()

      return difflib.ndiff(fromData.split("\n"), toData.split("\n"))
    return []

  def mergeWithServer(self):
    retVal = self.rpcClient.getCompressedData()
    if retVal['OK']:
      remoteCFG = CFG()
      data = retVal['Value']
      if six.PY3 and isinstance(data, str):
        data = data.encode(errors="surrogateescape")
      remoteCFG.loadFromBuffer(zlib.decompress(data).decode())
      serverVersion = gConfigurationData.getVersion(remoteCFG)
      self.cfgData = remoteCFG.mergeWith(self.cfgData)
      gConfigurationData.setVersion(serverVersion, self.cfgData)
    return retVal

  def rollbackToVersion(self, version):
    return self.rpcClient.rollbackToVersion(version)

  def updateGConfigurationData(self):
    gConfigurationData.setRemoteCFG(self.cfgData)
