/*
 * Copyright 2009 - 2010 Kevin Ackley (kackley@gwi.net)
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "E57FormatImpl.h"
#include "ImageFileImpl.h"
#include "NodeImpl.h"
#include "SourceDestBufferImpl.h"


using namespace e57;
using namespace std;


NodeImpl::NodeImpl(ImageFileImplWeakPtr destImageFile)
: destImageFile_(destImageFile),
  isAttached_(false)
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));  // does checking for all node type ctors
}

void NodeImpl::checkImageFileOpen(const char* srcFileName, int srcLineNumber, const char* srcFunctionName) const
{
    /// Throw an exception if destImageFile (destImageFile_) isn't open
    ImageFileImplSharedPtr destImageFile(destImageFile_);
    if (!destImageFile->isOpen())
    {
        throw E57Exception(E57_ERROR_IMAGEFILE_NOT_OPEN,
                           "fileName=" + destImageFile->fileName(),
                           srcFileName,
                           srcLineNumber,
                           srcFunctionName);
    }
}

bool NodeImpl::isRoot() const
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

    return parent_.expired();
};

NodeImplSharedPtr NodeImpl::parent()
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

    if (isRoot())
    {
        /// If is root, then has self as parent (by convention)
        return shared_from_this();
    }

    NodeImplSharedPtr myParent(parent_);

    return myParent;
}

ustring NodeImpl::pathName() const
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

    if (isRoot())
    {
        return("/");
    }

    NodeImplSharedPtr p(parent_);

    if (p->isRoot())
    {
        return("/" + elementName_);
    }

    return(p->pathName() + "/" + elementName_);
}

ustring NodeImpl::relativePathName(const NodeImplSharedPtr &origin, ustring childPathName) const
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));
    if (origin == shared_from_this())
    {
       return(childPathName);
    }

    if (isRoot())
    {
       /// Got to top and didn't find origin, must be error
       throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "this->elementName=" + this->elementName() + " childPathName="+childPathName);
    }

    /// Assemble relativePathName from right to left, recursively
    NodeImplSharedPtr p( parent_ );

    if ( childPathName.empty() )
    {
       return p->relativePathName( origin, elementName_ );
    }

    return p->relativePathName( origin, elementName_ + "/" + childPathName );
}

ustring NodeImpl::elementName() const
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

    return elementName_;
}

ImageFileImplSharedPtr NodeImpl::destImageFile()
{
    /// don't checkImageFileOpen
    return ImageFileImplSharedPtr( destImageFile_ );
}

bool NodeImpl::isAttached() const
{
    checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

    return isAttached_;
}

void NodeImpl::setAttachedRecursive()
{
    /// Non-terminal node types (Structure, Vector, CompressedVector) will override this virtual function, to mark their children, codecs, prototypes
    isAttached_ = true;
}

ustring NodeImpl::imageFileName() const
{
    /// don't checkImageFileOpen
    ImageFileImplSharedPtr imf(destImageFile_);

    return imf->fileName();
}

void NodeImpl::setParent(NodeImplSharedPtr parent, const ustring& elementName)
{
    /// don't checkImageFileOpen

    /// First check if our parent_ is already set, throw E57_ERROR_ALREADY_HAS_PARENT
    /// The isAttached_ condition is to catch two errors:
    ///    1) if user attempts to use the ImageFile root as a child (e.g. root.set("x", root))
    ///    2) if user attempts to reuse codecs or prototype trees of a CompressedVectorNode
    ///       ??? what if CV not attached yet?
    if (!parent_.expired() || isAttached_) {
        /// ??? does caller do setParent first, so state is not messed up when throw?
        throw E57_EXCEPTION2(E57_ERROR_ALREADY_HAS_PARENT,
                             "this->pathName=" + this->pathName() +
                             " newParent->pathName=" + parent->pathName());
    }

    parent_      = parent;
    elementName_ = elementName;

    /// If parent is attached then we are attached (and all of our children)
    if (parent->isAttached())
        setAttachedRecursive();
}

NodeImplSharedPtr NodeImpl::getRoot()
{
    /// don't checkImageFileOpen
    NodeImplSharedPtr p(shared_from_this());
    while (!p->isRoot())
    {
        p = NodeImplSharedPtr(p->parent_);  //??? check if bad ptr?
    }

    return p;
}

//??? use visitor?
bool NodeImpl::isTypeConstrained()
{
    /// don't checkImageFileOpen
    /// A node is type constrained if any of its parents is an homo VECTOR or COMPRESSED_VECTOR with more than one child
    NodeImplSharedPtr p(shared_from_this());

    while (!p->isRoot()) {
        /// We have a parent since we are not root
        p = NodeImplSharedPtr(p->parent_);  //??? check if bad ptr?

        switch (p->type()) {
            case E57_VECTOR:
                {
                    /// Downcast to shared_ptr<VectorNodeImpl>
                    shared_ptr<VectorNodeImpl> ai(dynamic_pointer_cast<VectorNodeImpl>(p));
                    if (!ai)  // check if failed
                        throw E57_EXCEPTION2(E57_ERROR_INTERNAL, "this->elementName=" + this->elementName() + " elementName="+p->elementName());

                    /// If homogenous vector and have more than one child, then can't change them
                    if (!ai->allowHeteroChildren() && ai->childCount() > 1)
                        return(true);
                }
                break;
            case E57_COMPRESSED_VECTOR:
                /// Can't make any type changes to CompressedVector prototype.  ??? what if hasn't been written to yet
                return(true);
            default:
                break;
        }
    }
    /// Didn't find any constraining VECTORs or COMPRESSED_VECTORs in path above us, so our type is not constrained.
    return(false);
}

NodeImplSharedPtr NodeImpl::get(const ustring& pathName)
{
    /// This is common virtual function for terminal E57 element types: Integer, ScaledInteger, Float, Blob.
    /// The non-terminal types override this virtual function.
    /// Only absolute pathNames make any sense here, because the terminal types can't have children, so relative pathNames are illegal.

   _verifyPathNameAbsolute( pathName );

    NodeImplSharedPtr root = _verifyAndGetRoot();

    /// Forward call to the non-terminal root node
    return root->get( pathName );
}

void NodeImpl::set(const ustring& pathName, NodeImplSharedPtr ni, bool autoPathCreate)
{
   /// This is common virtual function for terminal E57 element types: Integer, ScaledInteger, Float, Blob.
   /// The non-terminal types override this virtual function.
   /// Only absolute pathNames make any sense here, because the terminal types can't have children, so relative pathNames are illegal.

   _verifyPathNameAbsolute( pathName );

   NodeImplSharedPtr root = _verifyAndGetRoot();

   /// Forward call to the non-terminal root node
   root->set( pathName, ni, autoPathCreate );
}

void NodeImpl::set(const StringList& /*fields*/, unsigned /*level*/, NodeImplSharedPtr /*ni*/, bool /*autoPathCreate*/)
{
    /// If get here, then tried to call set(fields...) on NodeImpl that wasn't a StructureNodeImpl, so that's an error
    throw E57_EXCEPTION1(E57_ERROR_BAD_PATH_NAME); //???
}

void NodeImpl::checkBuffers(const vector<SourceDestBuffer>& sdbufs, bool allowMissing)  //??? convert sdbufs to vector of shared_ptr
{
    /// this node is prototype of CompressedVector

    /// don't checkImageFileOpen

    StringSet pathNames;

    for (unsigned i = 0; i < sdbufs.size(); i++) {
        ustring pathName = sdbufs.at(i).impl()->pathName();

        /// Check that all buffers are same size
        if (sdbufs.at(i).impl()->capacity() != sdbufs.at(0).impl()->capacity()) {
            throw E57_EXCEPTION2(E57_ERROR_BUFFER_SIZE_MISMATCH,
                                 "this->pathName=" + this->pathName()
                                 + " sdbuf.pathName=" + pathName
                                 + " firstCapacity=" + toString(sdbufs.at(0).impl()->capacity())
                                 + " secondCapacity=" + toString(sdbufs.at(i).impl()->capacity()));
        }

        /// Add each pathName to set, error if already in set (a duplicate pathName in sdbufs)
        if (!pathNames.insert(pathName).second)
            throw E57_EXCEPTION2(E57_ERROR_BUFFER_DUPLICATE_PATHNAME, "this->pathName=" + this->pathName() + " sdbuf.pathName=" + pathName);

        /// Check no bad fields in sdbufs
        if (!isDefined(pathName))
            throw E57_EXCEPTION2(E57_ERROR_PATH_UNDEFINED, "this->pathName=" + this->pathName() + " sdbuf.pathName=" + pathName);
    }

    if (!allowMissing) {
        /// Traverse tree recursively, checking that all nodes are listed in sdbufs
        checkLeavesInSet(pathNames, shared_from_this());
    }
}

bool NodeImpl::findTerminalPosition(const NodeImplSharedPtr &target, uint64_t& countFromLeft)
{
    /// don't checkImageFileOpen

    if (this == &*target) //??? ok?
    {
       return true;
    }

    switch (type())
    {
        case E57_STRUCTURE:
            {
                auto sni = dynamic_cast<StructureNodeImpl*>(this);

                /// Recursively visit child nodes
                if ( sni != nullptr )
                {
                   int64_t childCount = sni->childCount();
                   for (int64_t i = 0; i < childCount; ++i)
                   {
                       if (sni->get(i)->findTerminalPosition(target, countFromLeft))
                       {
                          return true;
                       }
                   }
                }
            }
            break;

        case E57_VECTOR:
            {
                auto vni = dynamic_cast<VectorNodeImpl*>(this);

                /// Recursively visit child nodes
                if ( vni != nullptr )
                {
                   int64_t childCount = vni->childCount();
                   for (int64_t i = 0; i < childCount; ++i)
                   {
                       if (vni->get(i)->findTerminalPosition(target, countFromLeft))
                       {
                          return true;
                       }
                   }
                }
            }
            break;

        case E57_COMPRESSED_VECTOR:
            break;  //??? for now, don't search into contents of compressed vector

        case E57_INTEGER:
        case E57_SCALED_INTEGER:
        case E57_FLOAT:
        case E57_STRING:
        case E57_BLOB:
            countFromLeft++;
            break;
    }

    return(false);
}

#ifdef E57_DEBUG
void NodeImpl::dump(int indent, ostream& os) const
{
    /// don't checkImageFileOpen
    os << space(indent) << "elementName: " << elementName_ << endl;
    os << space(indent) << "isAttached:  " << isAttached_ << endl;
    os << space(indent) << "path:        " << pathName() << endl;
}

bool NodeImpl::_verifyPathNameAbsolute( const ustring &inPathName )
{
   checkImageFileOpen(__FILE__, __LINE__, static_cast<const char *>(__FUNCTION__));

   /// Parse to determine if pathName is absolute
   bool isRelative = false;
   vector<ustring> fields;
   ImageFileImplSharedPtr imf( destImageFile_ );

   imf->pathNameParse( inPathName, isRelative, fields );  // throws if bad pathName

   /// If not an absolute path name, have error
   if ( isRelative )
   {
      throw E57_EXCEPTION2( E57_ERROR_BAD_PATH_NAME, "this->pathName=" + this->pathName() + " pathName=" + inPathName );
   }

   return isRelative;
}

NodeImplSharedPtr NodeImpl::_verifyAndGetRoot()
{
   /// Find root of the tree
   NodeImplSharedPtr root( shared_from_this()->getRoot() );

   /// Check to make sure root node is non-terminal type (otherwise have stack overflow).
   switch (root->type()) {
       case E57_STRUCTURE:
       case E57_VECTOR: //??? COMPRESSED_VECTOR?
           break;
       default:
           throw E57_EXCEPTION2( E57_ERROR_INTERNAL, "root invalid for this->pathName=" + this->pathName() );
   }

   return root;
}
#endif
