/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ZkTreeUtil.h"

#include <map>
#include <iostream>
#include <log4cxx/logger.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>

namespace zktreeutil
{
    using std::map;
    using std::pair;

    static ZkTreeNodeSptr loadZkTree_ (ZooKeeperAdapterSptr zkHandle,
            const string& path)
    {
        // Extract the node value
        string value = zkHandle->getNodeData(path);

        // Extract nodename from the path
        string nodename = "/";
        if (path != "/")
        {
            vector< string > nodes;
            boost::split(nodes, path, boost::is_any_of ("/") );
            nodename = nodes[nodes.size()-1];
        }

        // Create tree-node with name and value
        ZkTreeNodeSptr nodeSptr = ZkTreeNodeSptr (new ZkTreeNode (nodename, value));
        std::cerr << "[zktreeutil] loaded nodename: "
            << nodename
            << " value: "
            << value
            << std::endl;

        // Load all the children
        vector< string > cnodes = zkHandle->getNodeChildren (path);
        for (unsigned i = 0; i < cnodes.size(); i++)
            nodeSptr->addChild (loadZkTree_ (zkHandle, cnodes[i]));

        // Return the constructed node
        return nodeSptr;
    }

    static ZkTreeNodeSptr loadZkTreeXml_ (xmlNode* xmlNodePtr)
    {
        // Null check
        if (xmlNodePtr == NULL)
        {
            std::cerr << "[zktreeutil] empty XML node encountered" << std::endl;
            exit (-1);
        }

        // Get the node name
        xmlChar* name = xmlGetProp (xmlNodePtr, BAD_CAST "name");
        string nameStr = (const char*)name;
        std::cerr << "[zktreeutil] node name: " << nameStr;
        xmlFree (name);
        // Get the node value
        string valueStr;
        xmlChar* value = xmlGetProp (xmlNodePtr, BAD_CAST "value");
        if (value)
        {
            valueStr = (const char*)value;
            std::cerr << " value: " << valueStr;
        }
        xmlFree (value);
        // Get the ignore flag
        bool doIgnore = false;
        xmlChar* ignore = xmlGetProp (xmlNodePtr, BAD_CAST "ignore");
        if (ignore)
        {
            string ignoreStr = (const char*) ignore;
            if (ignoreStr == "true" || ignoreStr == "yes" || ignoreStr == "1")
            {
                doIgnore = true;
                std::cerr << " <ignore:>";
            }
        }
        xmlFree (ignore);
        std::cerr << std::endl;

        // Create the zk node
        ZkTreeNodeSptr nodeSptr =
            ZkTreeNodeSptr (new ZkTreeNode (nameStr,
                        ZkNodeData (valueStr, doIgnore)));

        // Load the children
        for (xmlNode* chldNode = xmlNodePtr->children;
                chldNode;
                chldNode = chldNode->next)
            if (chldNode->type == XML_ELEMENT_NODE)
                nodeSptr->addChild (loadZkTreeXml_ (chldNode));

        // Return the loaded node
        return nodeSptr;
    }

    static void writeZkTree_ (ZooKeeperAdapterSptr zkHandle,
            const ZkTreeNodeSptr zkNodeSptr,
            const string& path)
    {
        // Create the path in zk-tree
        zkHandle->createNode(path.c_str(), "", 0, false);
        std::cerr << "[zktreeutil] created key: " << path << std::endl;
        // Set value for the path
        string value = zkNodeSptr->getData().value;
        if (value != "")
        {
            zkHandle->setNodeData (path.c_str(), value.c_str());
            std::cerr << "[zktreeutil] set value: " << std::endl;
        }

        // Go deep to write the subtree rooted in the node, if not to be ignored
        if (!(zkNodeSptr->getData().ignoreUpdate))
        {
            for (unsigned i=0; i < zkNodeSptr->numChildren(); i++)
            {
                ZkTreeNodeSptr childNodeSptr = zkNodeSptr->getChild (i);
                // Add the node name into the path and write in zk-tree
                string cpath = ((path != "/")? path : "")
                    + string("/")
                    + childNodeSptr->getKey();
                writeZkTree_ (zkHandle, childNodeSptr, cpath);
            }
        }

        return;
    }

    static void addTreeZkAction_ (const ZkTreeNodeSptr zkNodeSptr,
            const string& path,
            vector< ZkAction >& actions)
    {
        // Create the key
        actions.push_back (ZkAction (ZkAction::CREATE, path));

        // Set value for the new key
        if (zkNodeSptr->getData().value != "")
            actions.push_back (ZkAction (ZkAction::VALUE,
                        path,
                        zkNodeSptr->getData().value));

        // Add all the children
        for (unsigned i=0; i < zkNodeSptr->numChildren(); i++)
        {
            ZkTreeNodeSptr childSptr = zkNodeSptr->getChild (i);
            string cpath = path + string("/") + childSptr->getKey();
            addTreeZkAction_ (childSptr, cpath, actions);
        }

        return;
    }

    static xmlNodePtr dumpZkTreeXml_ (const ZkTreeNodeSptr zkNodeSptr)
    {
        // Create xml node with zknode name and value
        string nodename = zkNodeSptr->getKey ();
        string value = zkNodeSptr->getData().value;
        xmlNodePtr node = xmlNewNode(NULL, BAD_CAST "zknode");
        xmlNewProp (node, BAD_CAST "name", BAD_CAST nodename.c_str());
        if (value.length())
            xmlNewProp (node, BAD_CAST "value", BAD_CAST value.c_str());

        // Add all the children rotted at this node
        for (unsigned i=0; i < zkNodeSptr->numChildren(); i++)
            xmlAddChild (node, dumpZkTreeXml_ (zkNodeSptr->getChild (i)));

        // Return xml node
        return node;
    }

    static void dumpZkTree_ (const ZkTreeNodeSptr zkNodeSptr,
            int maxLevel,
            int level,
            vector< bool >& masks)
    {
        // Check the max. dlevel to be dumped
        if (level > maxLevel)
            return;

        
        // Create branch
        for (int i=0; i < level; i++) 
        {
            if ( i== level-1) std::cout << "|   ";
            else if (masks[i]) std::cout << "    ";
            else std::cout << "|   ";
        }
        std::cout << std::endl;
        for (int i=0; i < level-1; i++)
        {
            if (masks[i]) std::cout << "    ";
            else std::cout << "|   ";
        }

        // Dump the node name and value
        std::cout << "|--[" << zkNodeSptr->getKey();
        if (zkNodeSptr->getData().value != "")
            std::cout << " => " << zkNodeSptr->getData().value;
        std::cout << "]" << std::endl;

        // Dump all the children
        for (unsigned i=0; i < zkNodeSptr->numChildren(); i++)
        {
            // Add mask for last child
            if (i == zkNodeSptr->numChildren()-1)
                masks.push_back(true);
            else
                masks.push_back(false);
            dumpZkTree_ (zkNodeSptr->getChild (i), maxLevel, level+1, masks);
        }

        masks.pop_back();
        return;
    }

    static ZkTreeNodeSptr traverseBranch_ (const ZkTreeNodeSptr& zkRootSptr,
            const string& path)
    {
        // Check if the tree is loaded into memory
        if (zkRootSptr == NULL)
        {
            string errMsg = "[zktreeutil] null root passed for traversing";
            std::cout << errMsg << std::endl;
            throw std::logic_error (errMsg);
        }

        // Split the path and add intermediate znodes
        vector< string > nodes;
        boost::split(nodes, path, boost::is_any_of ("/") );

        // Start traversing the tree
        ZkTreeNodeSptr currNodeSptr = zkRootSptr;
        for (unsigned znode_idx = 1; znode_idx < nodes.size(); znode_idx++)
        {
            bool found = false;
            for (unsigned i=0; i < currNodeSptr->numChildren(); i++)
            {
                ZkTreeNodeSptr  childNodeSptr = currNodeSptr->getChild(i);
                if (childNodeSptr->getKey() == nodes[znode_idx])
                {
                    // Found! go to the znode
                    currNodeSptr = childNodeSptr;
                    found = true;
                    break;
                }
            }
            if (!found) // No such znode found; return NULL node-ptr
            {
                string errMsg = string("[zktreeutil] unknown znode during traversal: ")
                    + nodes[znode_idx];
                std::cout << errMsg << std::endl;
                throw std::logic_error (errMsg);
            }
        }

        return currNodeSptr;
    }

    static ZkTreeNodeSptr createAncestors_ (const string& path)
    {
        // Create the root znode
        ZkTreeNodeSptr zkRootSptr = ZkTreeNodeSptr (new ZkTreeNode ("/"));
        ZkTreeNodeSptr currNodeSptr = zkRootSptr;
        // Split the path and add intermediate znodes
        vector< string > nodes;
        boost::split(nodes, path, boost::is_any_of ("/") );
        for (unsigned i=1; i < nodes.size()-1; i++)
        {
            ZkTreeNodeSptr childNodeSptr = ZkTreeNodeSptr (new ZkTreeNode (nodes[i]));
            currNodeSptr->addChild (childNodeSptr);
            currNodeSptr = childNodeSptr;
        }

        //Return the root of the branch
        return zkRootSptr;
    }

    ZooKeeperAdapterSptr ZkTreeUtil::get_zkHandle (const string& zkHosts)
    {
        try
        {
            // Create an instance of ZK adapter.
            ZooKeeperConfig config (zkHosts, 10000);
            ZooKeeperAdapterSptr zkHandleSptr =
                ZooKeeperAdapterSptr (new ZooKeeperAdapter (config));
            return zkHandleSptr;
        }
        catch (const ZooKeeperException &e)
        {
            std::cerr << "[zktreeutil] zooKeeper exception caught: "
                << e.what()
                << std::endl;
            throw;
        }
        catch (std::exception &stde)
        {
            std::cerr << "[zktreeutil] standard exception caught: "
                << stde.what()
                << std::endl;
            throw;
        }
        catch (...)
        {
            std::cerr
                << "[zktreeutil] unknown exception while connecting to zookeeper"
                << std::endl;
            throw;
        }
    }


    void ZkTreeUtil::loadZkTree (const string& zkHosts,
            const string& path,
            bool force)
    {
        // Check if already loaded
        if (loaded_ && !force)
        {
            std::cerr << "[zktreeutil] zk-tree already loaded into memory"
                << std::endl;
            return;
        }

        // Connect to ZK server
        ZooKeeperAdapterSptr zkHandle = get_zkHandle (zkHosts);
        std::cerr << "[zktreeutil] connected to ZK serverfor reading"
            << std::endl;

        // Check the existance of the path to znode
        if (!zkHandle->nodeExists (path))
        {
            string errMsg = string("[zktreeutil] path does not exists : ") + path;
            std::cout << errMsg << std::endl;
            throw std::logic_error (errMsg);
        }

        // Load the rooted (sub)tree
        ZkTreeNodeSptr zkSubrootSptr = loadZkTree_ (zkHandle, path);

        //  Create the ancestors before loading the rooted subtree
        if (path != "/")
        {
            zkRootSptr_ = createAncestors_(path);
            string ppath = path.substr (0, path.rfind('/'));
            ZkTreeNodeSptr parentSptr = traverseBranch_( zkRootSptr_, ppath);
            parentSptr->addChild (zkSubrootSptr);
        }
        else // Loaded entire zk-tree
        {
            zkRootSptr_ = zkSubrootSptr;
        }

        // Set load flag
        loaded_ = true;
        return;
    }

    void ZkTreeUtil::loadZkTreeXml (const string& zkXmlConfig,
            bool force)
    {
        // Check if already loaded
        if (loaded_ && !force)
        {
            std::cerr << "[zktreeutil] zk-tree already loaded into memory"
                << std::endl;
            return;
        }

        // Parse the file and get the DOM
        xmlDocPtr docPtr = xmlReadFile(zkXmlConfig.c_str(), NULL, 0);
        if (docPtr == NULL) {
            std::cerr << "[zktreeutil] could not parse XML file "
                << zkXmlConfig
                << std::endl;
            exit (-1);
        }
        std::cerr << "[zktreeutil] zk-tree XML parsing successful"
            << std::endl;

        // Get the root element node
        xmlNodePtr rootPtr = xmlDocGetRootElement(docPtr);
        // Create the root zk node
        zkRootSptr_ = ZkTreeNodeSptr (new ZkTreeNode ("/"));
        // Load the rooted XML tree
        for (xmlNode* chldNode = rootPtr->children;
                chldNode;
                chldNode = chldNode->next)
        {
            if (chldNode->type == XML_ELEMENT_NODE)
                zkRootSptr_->addChild (loadZkTreeXml_ (chldNode));
        }

        // set oad flag
        loaded_ = true;
        // Cleanup stuff
        xmlFreeDoc(docPtr);
        xmlCleanupParser();
        return;
    }

    void ZkTreeUtil::writeZkTree (const string& zkHosts,
            const string& path,
            bool force) const
    {
        // Connect to ZK server
        ZooKeeperAdapterSptr zkHandle = get_zkHandle (zkHosts);
        std::cerr << "[zktreeutil] connected to ZK server for writing"
            << std::endl;

        // Go to the rooted subtree
        ZkTreeNodeSptr zkRootSptr = traverseBranch_ (zkRootSptr_, path);

        // Cleanup before write if forceful write enabled
        if (force)
        {
            if (path != "/") // remove the subtree rooted at the znode
            {
                // Delete the subtree rooted at the znode before write
                if (zkHandle->nodeExists (path))
                {
                    std::cerr << "[zktreeutil] deleting subtree rooted at "
                        << path
                        << "..."
                        << std::endl;
                    zkHandle->deleteNode (path, true);
                }
            }
            else // remove the rooted znodes
            {
                std::cerr << "[zktreeutil] deleting rooted zk-tree"
                    << "..."
                    << std::endl;
                // Get the root's children
                vector< string > cnodes = zkHandle->getNodeChildren ("/");
                for (unsigned i=0; i < cnodes.size(); i++)
                {
                    if ( cnodes[i] != "/zookeeper") // reserved for zookeeper use
                        zkHandle->deleteNode(cnodes[i], true);
                }
            }
        }

        // Start tree construction
        writeZkTree_ (zkHandle, zkRootSptr, path);
        return;
    }

    void ZkTreeUtil::dumpZkTree (bool xml, int depth) const
    {
        if (xml)
        {
            // Creates a new document, a node and set it as a root node
            xmlDocPtr docPtr = xmlNewDoc(BAD_CAST "1.0");
            xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "root");
            xmlDocSetRootElement(docPtr, rootNode);

            // Add all the rooted children
            for (unsigned i=0; i < zkRootSptr_->numChildren(); i++)
                xmlAddChild (rootNode, dumpZkTreeXml_ (zkRootSptr_->getChild (i)));

            // Dumping document to stdio or file
            xmlSaveFormatFileEnc("-", docPtr, "UTF-8", 1);

            // Cleanup stuff
            xmlFreeDoc(docPtr);
            xmlCleanupParser();
            return;
        }

        // Dump text
        std::cout << "/" << std::endl;
        vector< bool > masks;
        for (unsigned i=0; i < zkRootSptr_->numChildren(); i++)
        {
            if (i == zkRootSptr_->numChildren()-1)
                masks.push_back(true);
            else
                masks.push_back(false);
            dumpZkTree_ (zkRootSptr_->getChild (i), depth, 1, masks);
        }

        return;
    }

    vector< ZkAction > ZkTreeUtil::diffZkTree (const string& zkHosts,
            const string& path) const
    {
        // Action container
        vector< ZkAction > actions;

        if (!loaded_)
        {
            std::cout << "[zktreeutil] zk-tree not loaded for diff"
                << std::endl;
            exit (-1);
        }

        // Load the rooted subtree from zookeeper
        ZooKeeperAdapterSptr zkHandle = get_zkHandle (zkHosts);
        std::cerr << "[zktreeutil] connected to ZK server for reading"
            << std::endl;
        ZkTreeNodeSptr zkLiveRootSptr = loadZkTree_ (zkHandle, path);

        // Go to the saved rooted subtree
        ZkTreeNodeSptr zkLoadedRootSptr =
            traverseBranch_ (zkRootSptr_, path);

        // Check the root value first
        if (zkLoadedRootSptr->getData().value
                != zkLiveRootSptr->getData().value)
        {
            actions.push_back (ZkAction (ZkAction::VALUE,
                        path,
                        zkLoadedRootSptr->getData().value,
                        zkLiveRootSptr->getData().value));
        }

        // Start traversal from root
        vector< string > ppaths;
        vector< pair< ZkTreeNodeSptr, ZkTreeNodeSptr > > commonNodes;
        ppaths.push_back ((path != "/")? path : "");
        commonNodes.push_back (pair< ZkTreeNodeSptr, ZkTreeNodeSptr >
                (zkLoadedRootSptr, zkLiveRootSptr));

        for (unsigned j=0; j < commonNodes.size(); j++)
        {
            // Get children of loaded tree
            map< string, ZkTreeNodeSptr > loadedChildren;
            for (unsigned i=0; i < commonNodes[j].first->numChildren(); i++)
            {
                ZkTreeNodeSptr childSptr = commonNodes[j].first->getChild (i);
                loadedChildren[childSptr->getKey()] = childSptr;
            }
            // Get children of live tree
            map< string, ZkTreeNodeSptr > liveChildren;
            for (unsigned i=0; i < commonNodes[j].second->numChildren(); i++)
            {
                ZkTreeNodeSptr childSptr = commonNodes[j].second->getChild (i);
                liveChildren[childSptr->getKey()] = childSptr;
            }

            // Start comparing the children
            for (map< string, ZkTreeNodeSptr >::const_iterator it =
                    loadedChildren.begin();
                    it != loadedChildren.end();
                    it++)
            {
                bool ignoreKey = it->second->getData().ignoreUpdate;
                string loadedVal = it->second->getData().value;
                // Path to this node
                string path = ppaths[j] + string("/") + it->first;

                map< string, ZkTreeNodeSptr >::const_iterator jt =
                    liveChildren.find (it->first);
                if (jt != liveChildren.end())
                {
                    // Key is present in live zk-tree
                    string liveVal = jt->second->getData().value;
                    // Check value for the key, if not ignored
                    if (!ignoreKey)
                    {
                        if (loadedVal != liveVal)
                        {
                            // Value differs, set the new value for the key
                            actions.push_back (ZkAction (ZkAction::VALUE,
                                        path,
                                        loadedVal,
                                        liveVal));
                        }

                        // Add node to common nodes
                        ppaths.push_back (path);
                        commonNodes.push_back (pair< ZkTreeNodeSptr, ZkTreeNodeSptr >
                                (it->second, jt->second));
                    }

                    // Remove the live zk node
                    liveChildren.erase (it->first);
                }
                else
                {
                    // Add the subtree rooted to this node, if not ignored
                    if (!ignoreKey)
                        addTreeZkAction_ (it->second, path, actions);
                }
            }

            // Remaining live zk nodes to be deleted
            for (map< string, ZkTreeNodeSptr >::const_iterator it = liveChildren.begin();
                    it != liveChildren.end(); it++)
            {
                string path = ppaths[j] + string("/") + it->first;
                actions.push_back (ZkAction (ZkAction::DELETE, path));
            }
        }
        // return the diff actions
        return actions;
    }

    void ZkTreeUtil::executeZkActions (const string& zkHosts,
            const vector< ZkAction >& zkActions,
            int execFlags) const
    {
        // Execute the diff zk actions
        if (zkActions.size())
        {
            // Connect to Zookeeper for writing
            ZooKeeperAdapterSptr zkHandleSptr;
            if ((execFlags & EXECUTE)
                    || (execFlags & INTERACTIVE))
            {
                zkHandleSptr = get_zkHandle (zkHosts);
                std::cerr << "[zktreeutil] connected to ZK server for writing"
                    << std::endl;
            }

            for (unsigned i=0; i < zkActions.size(); i++)
            {
                if (zkActions[i].action == ZkAction::CREATE)
                {
                    if (execFlags & PRINT)
                        std::cout << "CREAT- key:" << zkActions[i].key << std::endl;
                    if (execFlags & EXECUTE)
                    {
                        if (execFlags & INTERACTIVE)
                        {
                            string resp;
                            std::cout << "Execute this action?[yes/no]: ";
                            std::getline(std::cin, resp);
                            if (resp != "yes")
                                continue;
                        }
                        zkHandleSptr->createNode(zkActions[i].key.c_str(), "", 0, false);
                    }
                }
                else if (zkActions[i].action == ZkAction::DELETE)
                {
                    if (execFlags & PRINT)
                        std::cout << "DELET- key:" << zkActions[i].key << std::endl;
                    if (execFlags & EXECUTE)
                    {
                        if (execFlags & INTERACTIVE)
                        {
                            string resp;
                            std::cout << "Execute this action?[yes/no]: ";
                            std::getline(std::cin, resp);
                            if (resp != "yes")
                                continue;
                        }
                        zkHandleSptr->deleteNode(zkActions[i].key.c_str(), true);
                    }
                }
                else if (zkActions[i].action == ZkAction::VALUE)
                {
                    if (execFlags & PRINT)
                    {
                        std::cout << "VALUE- key:"
                            << zkActions[i].key
                            << " value:" << zkActions[i].newval;
                        if (zkActions[i].oldval != "")
                            std::cout << " old_value:" << zkActions[i].oldval;
                        std::cout << std::endl;
                    }
                    if (execFlags & EXECUTE)
                    {
                        if (execFlags & INTERACTIVE)
                        {
                            string resp;
                            std::cout << "Execute this action?[yes/no]: ";
                            std::getline(std::cin, resp);
                            if (resp != "yes")
                                continue;
                        }
                        zkHandleSptr->setNodeData (zkActions[i].key, zkActions[i].newval);
                    }
                }
            }
        }

        return;
    }

}

