/**
 * 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 "core/render/node/render_object.h"
#include <math.h>

#include "core/common/view_utils.h"
#include "core/css/constants_name.h"
#include "core/css/constants_value.h"
#include "core/css/css_value_getter.h"
#include "core/layout/layout.h"
#include "core/manager/weex_core_manager.h"
#include "core/render/manager/render_manager.h"
#include "core/render/page/render_page.h"

namespace WeexCore {

RenderObject::RenderObject() : parent_render_(nullptr) {
  this->styles_ = new std::map<std::string, std::string>();
  this->attributes_ = new std::map<std::string, std::string>();
  this->events_ = new std::set<std::string>();
  this->is_root_render_ = false;
}

RenderObject::~RenderObject() {
  this->parent_render_ = nullptr;

  if (this->styles_ != nullptr) {
    delete this->styles_;
    this->styles_ = nullptr;
  }

  if (this->attributes_ != nullptr) {
    delete this->attributes_;
    this->attributes_ = nullptr;
  }

  if (this->events_ != nullptr) {
    delete this->events_;
    this->events_ = nullptr;
  }

  for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
    RenderObject *child = static_cast<RenderObject *>(*it);
    if (child != nullptr) {
      delete child;
    }
  }

  for (auto it : shadow_objects_) {
      delete it;
  }
}

void RenderObject::ApplyDefaultStyle(bool reserve) {
  std::map<std::string, std::string> *style = GetDefaultStyle();

  if (style == nullptr) return;

  for (auto iter = style->cbegin(); iter != style->cend(); iter++)
    AddStyle(iter->first, iter->second, reserve);

  if (style != nullptr) {
    delete style;
  }
}

RenderObject* RenderObject::RichtextParent() {
    if (parent_render_ && parent_render_->type() == "richtext") {
        return parent_render_;
    } else if (parent_render_) {
        return parent_render_->RichtextParent();
    }
    return nullptr;
}

void RenderObject::ApplyDefaultAttr() {
  std::map<std::string, std::string> *attrs = GetDefaultAttr();

  if (attrs == nullptr) return;

  for (auto iter = attrs->cbegin(); iter != attrs->cend(); iter++) {
    UpdateAttr(iter->first, iter->second);
  }

  if (attrs != nullptr) {
    delete attrs;
  }
}

static WXCoreSize measureFunc_Impl(WXCoreLayoutNode *node, float width,
                            MeasureMode widthMeasureMode, float height,
                            MeasureMode heightMeasureMode) {
  WXCoreSize size;
  size.height = 0;
  size.width = 0;

  if (!node->haveMeasureFunc()) return size;
  return WeexCoreManager::Instance()
      ->getPlatformBridge()
      ->platform_side()
      ->InvokeMeasureFunction(
          static_cast<RenderObject *>(node)->page_id().c_str(),
          reinterpret_cast<intptr_t>(node), width, widthMeasureMode, height,
          heightMeasureMode);
}

void RenderObject::BindMeasureFunc() { setMeasureFunc(measureFunc_Impl); }

void RenderObject::OnLayoutBefore() {
  if (!haveMeasureFunc()) return;
  WeexCoreManager::Instance()
      ->getPlatformBridge()
      ->platform_side()
      ->InvokeLayoutBefore(page_id().c_str(), reinterpret_cast<intptr_t>(this));
}
    
void RenderObject::OnLayoutPlatform() {
  if (!getNeedsPlatformDependentLayout()) return;
  WeexCoreManager::Instance()
    ->getPlatformBridge()
    ->platform_side()
    ->InvokeLayoutPlatform(page_id().c_str(), reinterpret_cast<intptr_t>(this));
}

void RenderObject::OnLayoutAfter(float width, float height) {
  if (!haveMeasureFunc()) return;
  WeexCoreManager::Instance()
      ->getPlatformBridge()
      ->platform_side()
      ->InvokeLayoutAfter(page_id().c_str(), reinterpret_cast<intptr_t>(this),
                          width, height);
}

StyleType RenderObject::ApplyStyle(const std::string &key,
                                   const std::string &value,
                                   const bool updating) {
  bool insert = false;
  if (value.length() > 0 && (value.at(0) == JSON_OBJECT_MARK_CHAR ||
                             value.at(0) == JSON_ARRAY_MARK_CHAR)) {
    MapInsertOrAssign(this->styles_, key, value);
    insert = true;
  }

  if (key == ALIGN_ITEMS) {
    setAlignItems(GetWXCoreAlignItem(value));
    return kTypeLayout;
  } else if (key == ALIGN_SELF) {
    setAlignSelf(GetWXCoreAlignSelf(value));
    return kTypeLayout;
  } else if (key == FLEX) {
    if (value.empty()) {
      set_flex(0);
    } else {
      float ret = getFloat(value.c_str());
      if (!isnan(ret)) {
        set_flex(ret);
      }
    }
    return kTypeLayout;
  } else if (key == DIRECTION) {
    WeexCore::WXCoreDirection direction = GetWXCoreDirection(value);
    if (direction ==  WeexCore::kDirectionInherit && this->is_root_render_ ) {
        direction = WeexCore::kDirectionLTR;
    } 
    setDirection(direction, updating);
    return kTypeInheritableLayout;
  } else if (key == FLEX_DIRECTION) {
    setFlexDirection(GetWXCoreFlexDirection(value), updating);
    return kTypeLayout;
  } else if (key == JUSTIFY_CONTENT) {
    setJustifyContent(GetWXCoreJustifyContent(value));
    return kTypeLayout;
  } else if (key == FLEX_WRAP) {
    setFlexWrap(GetWXCoreFlexWrap(value));
    return kTypeLayout;
  } else if (key == MIN_WIDTH) {
    UpdateStyleInternal(key, value, NAN,
                        [=](float foo) { setMinWidth(foo, updating); });
    return kTypeLayout;
  } else if (key == MIN_HEIGHT) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) { setMinHeight(foo); });
    return kTypeLayout;
  } else if (key == MAX_WIDTH) {
    UpdateStyleInternal(key, value, NAN,
                        [=](float foo) { setMaxWidth(foo, updating); });
    return kTypeLayout;
  } else if (key == MAX_HEIGHT) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) { setMaxHeight(foo); });
    return kTypeLayout;
  } else if (key == HEIGHT) {
    if (UpdateStyleInternal(key, value, NAN,
                            [=](float foo) { setStyleHeight(foo); })) {
      setStyleHeightLevel(CSS_STYLE);
    }
    return kTypeLayout;
  } else if (key == WIDTH) {
    if (UpdateStyleInternal(key, value, NAN,
                            [=](float foo) { setStyleWidth(foo, updating); })) {
      setStyleWidthLevel(CSS_STYLE);
    }
    return kTypeLayout;
  } else if (key == POSITION) {
    setStylePositionType(GetWXCorePositionType(value));
    if (value == STICKY) {
      this->is_sticky_ = true;
    }
    MapInsertOrAssign(this->styles_, key, value);
    return kTypeStyle;
  } else if (key == LEFT) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) {
      setStylePosition(kPositionEdgeLeft, foo);
    });
    return kTypeLayout;
  } else if (key == TOP) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) {
      setStylePosition(kPositionEdgeTop, foo);
    });
    return kTypeLayout;
  } else if (key == RIGHT) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) {
      setStylePosition(kPositionEdgeRight, foo);
    });
    return kTypeLayout;
  } else if (key == BOTTOM) {
    UpdateStyleInternal(key, value, NAN, [=](float foo) {
      setStylePosition(kPositionEdgeBottom, foo);
    });
    return kTypeLayout;
  } else if (key == MARGIN) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setMargin(kMarginALL, foo); });
    return kTypeMargin;
  } else if (key == MARGIN_LEFT) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setMargin(kMarginLeft, foo); });
    return kTypeMargin;
  } else if (key == MARGIN_TOP) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setMargin(kMarginTop, foo); });
    return kTypeMargin;
  } else if (key == MARGIN_RIGHT) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setMargin(kMarginRight, foo); });
    return kTypeMargin;
  } else if (key == MARGIN_BOTTOM) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setMargin(kMarginBottom, foo); });
    return kTypeMargin;
  } else if (key == BORDER_WIDTH) {
    UpdateStyleInternal(key, value, 0, [=](float foo) {
      setBorderWidth(kBorderWidthALL, foo);
    });
    return kTypeBorder;
  } else if (key == BORDER_TOP_WIDTH) {
    UpdateStyleInternal(key, value, 0, [=](float foo) {
      setBorderWidth(kBorderWidthTop, foo);
    });
    return kTypeBorder;
  } else if (key == BORDER_RIGHT_WIDTH) {
    UpdateStyleInternal(key, value, 0, [=](float foo) {
      setBorderWidth(kBorderWidthRight, foo);
    });
    return kTypeBorder;
  } else if (key == BORDER_BOTTOM_WIDTH) {
    UpdateStyleInternal(key, value, 0, [=](float foo) {
      setBorderWidth(kBorderWidthBottom, foo);
    });
    return kTypeBorder;
  } else if (key == BORDER_LEFT_WIDTH) {
    UpdateStyleInternal(key, value, 0, [=](float foo) {
      setBorderWidth(kBorderWidthLeft, foo);
    });
    return kTypeBorder;
  } else if (key == PADDING) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setPadding(kPaddingALL, foo); });
    return kTypePadding;
  } else if (key == PADDING_LEFT) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setPadding(kPaddingLeft, foo); });
    return kTypePadding;
  } else if (key == PADDING_TOP) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setPadding(kPaddingTop, foo); });
    return kTypePadding;
  } else if (key == PADDING_RIGHT) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setPadding(kPaddingRight, foo); });
    return kTypePadding;
  } else if (key == PADDING_BOTTOM) {
    UpdateStyleInternal(key, value, 0,
                        [=](float foo) { setPadding(kPaddingBottom, foo); });
    return kTypePadding;
  } else {
    if (!insert) {
      MapInsertOrAssign(this->styles_, key, value);
    }
    return kTypeStyle;
  }
}

const std::string RenderObject::GetStyle(const std::string &key) {
  if (this->styles_ == nullptr) return "";

  std::map<std::string, std::string>::iterator iter = this->styles_->find(key);
  if (iter != this->styles_->end()) {
    return iter->second;
  } else {
    return "";
  }
}

const std::string RenderObject::GetAttr(const std::string &key) {
  if (this->attributes_ == nullptr) return "";

  std::map<std::string, std::string>::iterator iter =
      this->attributes_->find(key);
  if (iter != this->attributes_->end()) {
    return iter->second;
  } else {
    return "";
  }
}

bool RenderObject::hasShadow(const RenderObject* shadow) const {
    if(std::find(shadow_objects_.begin(), shadow_objects_.end(), shadow) != shadow_objects_.end()){
        return true;
    }else{
        return false;
    }
}

int RenderObject::AddRenderObject(int index, RenderObject *child) {
  if (child == nullptr || index < -1) {
    return index;
  }

  if (type() == "richtext") {
      if (!hasShadow(child)) {
          shadow_objects_.push_back(child);
          child->set_parent_render(this);
      }
      return index;
  }

  Index count = getChildCount();
  index = index >= count ? -1 : index;
  if (index == -1) {
    addChildAt(child, getChildCount());
  } else {
    addChildAt(child, index);
  }

  child->set_parent_render(this);

  return index;
}

Index RenderObject::IndexOf(const RenderObject *render) {
  if (render == nullptr) {
    return -1;
  } else {
    int i = 0;
    for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
      RenderObject *child = static_cast<RenderObject *>(*it);
      if (child != nullptr) {
        if (render->ref() == child->ref()) return i;
      }
      ++i;
    }
  }
  return -1;
}

bool RenderObject::UpdateStyleInternal(const std::string key,
                                       const std::string value, float fallback,
                                       std::function<void(float)> functor) {
  bool ret = false;
  if (value.empty()) {
    functor(fallback);
    ret = true;
  } else {
    float fvalue = getFloatByViewport(value,
                                      RenderManager::GetInstance()->viewport_width(page_id()),
                                      RenderManager::GetInstance()->DeviceWidth(page_id()),
#if OS_IOS
                                      // reduce a map search on iOS
                                      false
#else
                                      RenderManager::GetInstance()->round_off_deviation(page_id())
#endif
                                      );
    if (!isnan(fvalue)) {
      functor(fvalue);
      ret = true;
    }
  }
  return ret;
}

void RenderObject::LayoutBeforeImpl() {
  if (isDirty()) {
    OnLayoutBefore();
  }

  for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
    RenderObject *child = static_cast<RenderObject *>(*it);
    if (child != nullptr) {
      child->LayoutBeforeImpl();
    }
  }
}
    
void RenderObject::LayoutPlatformImpl() {
  if (hasNewLayout()) {
    OnLayoutPlatform();
  }

  for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
    RenderObject *child = static_cast<RenderObject *>(*it);
    if (child != nullptr) {
      child->LayoutPlatformImpl();
    }
  }
}

void RenderObject::LayoutAfterImpl() {
  if (hasNewLayout()) {
    OnLayoutAfter(getLayoutWidth(), getLayoutHeight());
  }

  for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
    RenderObject *child = static_cast<RenderObject *>(*it);
    if (child != nullptr) {
      child->LayoutAfterImpl();
    }
  }
}

void RenderObject::CopyFrom(RenderObject *src) {
  IRenderObject::CopyFrom(src);
  this->styles_->insert(src->styles_->begin(), src->styles_->end());
  this->attributes_->insert(src->attributes_->begin(), src->attributes_->end());
  this->events_->insert(src->events_->begin(), src->events_->end());
}

void RenderObject::MapInsertOrAssign(
    std::map<std::string, std::string> *targetMap, const std::string &key,
    const std::string &value) {
  std::map<std::string, std::string>::iterator it = targetMap->find(key);
  if (it != targetMap->end()) {
    it->second = value;
  } else {
    targetMap->insert({key, value});
  }
}

bool RenderObject::ViewInit() {
  return (!isnan(getStyleWidth()) && getStyleWidth() > 0) ||
         (is_root_render() && GetRenderPage() != nullptr &&
          GetRenderPage()->is_render_container_width_wrap_content());
}

RenderPage *RenderObject::GetRenderPage() {
  return static_cast<RenderPage*>(RenderManager::GetInstance()->GetPage(page_id()));
}

bool RenderObject::IsAppendTree() {
  std::string append = GetAttr(APPEND);
  if (append == "tree") {
    return true;
  }
  return false;
}

void RenderObject::UpdateAttr(std::string key, std::string value) {
  MapInsertOrAssign(this->attributes_, key, value);
}

StyleType RenderObject::UpdateStyle(std::string key, std::string value) {
  return ApplyStyle(key, value, true);
}
  
void RenderObject::MergeStyles(std::vector<std::pair<std::string, std::string>> *src) {
  if (src) {
    for (auto& p : *src) {
      MapInsertOrAssign(styles_, p.first, p.second);
    }
  }
}

RenderObject *RenderObject::GetChild(const Index &index) {
  return static_cast<RenderObject *>(getChildAt(index));
}

void RenderObject::RemoveRenderObject(RenderObject *child) {
    if (type() == "richtext") {
        int index = 0;
        for (auto it : shadow_objects_) {
            if (it == child) {
                shadow_objects_.erase(shadow_objects_.begin() + index);
                return;
            }
            index++;
        }
    } else {
          removeChild(child);
    }
}

void RenderObject::AddAttr(std::string key, std::string value) {
  MapInsertOrAssign(this->attributes_, key, value);
}

StyleType RenderObject::AddStyle(std::string key, std::string value, bool reserve) {
  if (reserve) {
    MapInsertOrAssign(styles_, key, value);
  }
  return ApplyStyle(key, value, false);
}

void RenderObject::AddEvent(std::string event) {
  if (this->events_ == nullptr) {
    this->events_ = new std::set<std::string>();
  }
  this->events_->insert(event);
}

void RenderObject::RemoveEvent(const std::string &event) {
  this->events_->erase(event);
}
}  // namespace WeexCore
