/**
 * 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.
 */
package org.apache.ambari.server.controller.internal;

import com.google.inject.Inject;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.ObjectNotFoundException;
import org.apache.ambari.server.StaticallyInject;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.WidgetResponse;
import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
import org.apache.ambari.server.controller.spi.NoSuchResourceException;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.RequestStatus;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.Resource.Type;
import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.orm.dao.WidgetDAO;
import org.apache.ambari.server.orm.dao.WidgetLayoutDAO;
import org.apache.ambari.server.orm.entities.WidgetEntity;
import org.apache.ambari.server.orm.entities.WidgetLayoutEntity;
import org.apache.ambari.server.orm.entities.WidgetLayoutUserWidgetEntity;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Resource provider for widget layout resources.
 */
@StaticallyInject
public class WidgetLayoutResourceProvider extends AbstractControllerResourceProvider {

  // ----- Property ID constants ---------------------------------------------

  public static final String WIDGETLAYOUT_ID_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "id");
  public static final String WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "cluster_name");
  public static final String WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "section_name");
  public static final String WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "layout_name");
  public static final String WIDGETLAYOUT_SCOPE_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "scope");
  public static final String WIDGETLAYOUT_WIDGETS_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "widgets");
  public static final String WIDGETLAYOUT_USERNAME_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "user_name");
  public static final String WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("WidgetLayoutInfo", "display_name");
  public static enum SCOPE {
    CLUSTER,
    USER
  }

  @SuppressWarnings("serial")
  private static Set<String> pkPropertyIds = new HashSet<String>() {
    {
      add(WIDGETLAYOUT_ID_PROPERTY_ID);
    }
  };

  @SuppressWarnings("serial")
  private static ReadWriteLock lock = new ReentrantReadWriteLock();

  @SuppressWarnings("serial")
  public static Set<String> propertyIds = new HashSet<String>() {
    {
      add(WIDGETLAYOUT_ID_PROPERTY_ID);
      add(WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID);
      add(WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID);
      add(WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID);
      add(WIDGETLAYOUT_WIDGETS_PROPERTY_ID);
      add(WIDGETLAYOUT_SCOPE_PROPERTY_ID);
      add(WIDGETLAYOUT_USERNAME_PROPERTY_ID);
      add(WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID);
    }
  };

  @SuppressWarnings("serial")
  public static Map<Type, String> keyPropertyIds = new HashMap<Type, String>() {
    {
      put(Type.WidgetLayout, WIDGETLAYOUT_ID_PROPERTY_ID);
      put(Type.Cluster, WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID);
      put(Type.User, WIDGETLAYOUT_USERNAME_PROPERTY_ID);
    }
  };

  @Inject
  private static WidgetDAO widgetDAO;

  @Inject
  private static WidgetLayoutDAO widgetLayoutDAO;

  /**
   * Create a new resource provider.
   *
   */
  public WidgetLayoutResourceProvider(AmbariManagementController managementController) {
    super(propertyIds, keyPropertyIds, managementController);
  }

  @Override
  public RequestStatus createResources(final Request request)
      throws SystemException,
      UnsupportedPropertyException,
      ResourceAlreadyExistsException,
      NoSuchParentResourceException {

    Set<Resource> associatedResources = new HashSet<Resource>();

    for (final Map<String, Object> properties : request.getProperties()) {
      WidgetLayoutEntity widgetLayoutEntity = createResources(new Command<WidgetLayoutEntity>() {

        @Override
        public WidgetLayoutEntity invoke() throws AmbariException {
          final String[] requiredProperties = {
              WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID,
              WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID,
              WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID,
              WIDGETLAYOUT_SCOPE_PROPERTY_ID,
              WIDGETLAYOUT_WIDGETS_PROPERTY_ID,
              WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID,
              WIDGETLAYOUT_USERNAME_PROPERTY_ID
          };
          for (String propertyName : requiredProperties) {
            if (properties.get(propertyName) == null) {
              throw new AmbariException("Property " + propertyName + " should be provided");
            }
          }
          final WidgetLayoutEntity entity = new WidgetLayoutEntity();
          String userName = getUserName(properties);

          Set widgetsSet = (LinkedHashSet) properties.get(WIDGETLAYOUT_WIDGETS_PROPERTY_ID);

          String clusterName = properties.get(WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID).toString();
          entity.setLayoutName(properties.get(WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID).toString());
          entity.setClusterId(getManagementController().getClusters().getCluster(clusterName).getClusterId());
          entity.setSectionName(properties.get(WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID).toString());
          entity.setScope(properties.get(WIDGETLAYOUT_SCOPE_PROPERTY_ID).toString());
          entity.setUserName(userName);
          entity.setDisplayName(properties.get(WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID).toString());

          List<WidgetLayoutUserWidgetEntity> widgetLayoutUserWidgetEntityList = new LinkedList<WidgetLayoutUserWidgetEntity>();
          int order=0;
          for (Object widgetObject : widgetsSet) {
            HashMap<String, Object> widget = (HashMap) widgetObject;
            long id = Integer.parseInt(widget.get("id").toString());
            WidgetEntity widgetEntity = widgetDAO.findById(id);
            if (widgetEntity == null) {
              throw new AmbariException("Widget with id " + widget.get("id").toString() + " does not exists");
            }
            WidgetLayoutUserWidgetEntity widgetLayoutUserWidgetEntity = new WidgetLayoutUserWidgetEntity();

            widgetLayoutUserWidgetEntity.setWidget(widgetEntity);
            widgetLayoutUserWidgetEntity.setWidgetOrder(order++);
            widgetLayoutUserWidgetEntity.setWidgetLayout(entity);
            widgetLayoutUserWidgetEntityList.add(widgetLayoutUserWidgetEntity);
            widgetEntity.getListWidgetLayoutUserWidgetEntity().add(widgetLayoutUserWidgetEntity);
          }

          entity.setListWidgetLayoutUserWidgetEntity(widgetLayoutUserWidgetEntityList);
          widgetLayoutDAO.create(entity);
          notifyCreate(Type.WidgetLayout, request);

          return entity;
        }
      });
      Resource resource = new ResourceImpl(Type.WidgetLayout);
      resource.setProperty(WIDGETLAYOUT_ID_PROPERTY_ID, widgetLayoutEntity.getId());
      associatedResources.add(resource);

    }

    return getRequestStatus(null, associatedResources);
  }

  @Override
  public Set<Resource> getResources(Request request, Predicate predicate)
      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
    final Set<Resource> resources = new HashSet<Resource>();
    final Set<String> requestedIds = getRequestPropertyIds(request, predicate);
    final Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);

    List<WidgetEntity> widgetEntities = new ArrayList<WidgetEntity>();
    List<WidgetLayoutEntity> layoutEntities = new ArrayList<WidgetLayoutEntity>();

    for (Map<String, Object> propertyMap: propertyMaps) {
      String userName = getUserName(propertyMap);
      if (propertyMap.get(WIDGETLAYOUT_ID_PROPERTY_ID) != null) {
        final Long id;
        try {
          id = Long.parseLong(propertyMap.get(WIDGETLAYOUT_ID_PROPERTY_ID).toString());
        } catch (Exception ex) {
          throw new SystemException("WidgetLayout should have numerical id");
        }
        final WidgetLayoutEntity entity = widgetLayoutDAO.findById(id);
        if (entity == null) {
          throw new NoSuchResourceException("WidgetLayout with id " + id + " does not exists");
        }
        layoutEntities.add(entity);
      } else {
        layoutEntities.addAll(widgetLayoutDAO.findAll());
      }
    }

    for (WidgetLayoutEntity layoutEntity : layoutEntities) {
      Resource resource = new ResourceImpl(Type.WidgetLayout);
      resource.setProperty(WIDGETLAYOUT_ID_PROPERTY_ID, layoutEntity.getId());
      String clusterName = null;
      try {
        clusterName = getManagementController().getClusters().getClusterById(layoutEntity.getClusterId()).getClusterName();
      } catch (AmbariException e) {
        throw new SystemException(e.getMessage());
      }
      resource.setProperty(WIDGETLAYOUT_CLUSTER_NAME_PROPERTY_ID, clusterName);
      resource.setProperty(WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID, layoutEntity.getLayoutName());
      resource.setProperty(WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID, layoutEntity.getSectionName());
      resource.setProperty(WIDGETLAYOUT_SCOPE_PROPERTY_ID, layoutEntity.getScope());
      resource.setProperty(WIDGETLAYOUT_USERNAME_PROPERTY_ID, layoutEntity.getUserName());
      resource.setProperty(WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID, layoutEntity.getDisplayName());

      List<HashMap> widgets = new ArrayList<HashMap>();
      List<WidgetLayoutUserWidgetEntity> widgetLayoutUserWidgetEntityList = layoutEntity.getListWidgetLayoutUserWidgetEntity();
      for (WidgetLayoutUserWidgetEntity widgetLayoutUserWidgetEntity : widgetLayoutUserWidgetEntityList) {
        WidgetEntity widgetEntity = widgetLayoutUserWidgetEntity.getWidget();
        HashMap<String, Object> widgetInfoMap = new HashMap<String, Object>();
        widgetInfoMap.put("WidgetInfo",WidgetResponse.coerce(widgetEntity));
        widgets.add(widgetInfoMap);
      }
      resource.setProperty(WIDGETLAYOUT_WIDGETS_PROPERTY_ID, widgets);

      resources.add(resource);
    }
    return resources;
  }

  @Override
  public RequestStatus updateResources(Request request, Predicate predicate)
    throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {

    final Set<Map<String, Object>> propertyMaps = request.getProperties();

    modifyResources(new Command<Void>() {
      @Override
      public Void invoke() throws AmbariException {
        for (Map<String, Object> propertyMap : propertyMaps) {
          final Long layoutId;
          try {
            layoutId = Long.parseLong(propertyMap.get(WIDGETLAYOUT_ID_PROPERTY_ID).toString());
          } catch (Exception ex) {
            throw new AmbariException("WidgetLayout should have numerical id");
          }
          lock.writeLock().lock();
          try {
            final WidgetLayoutEntity entity = widgetLayoutDAO.findById(layoutId);
            if (entity == null) {
              throw new ObjectNotFoundException("There is no widget layout with id " + layoutId);
            }
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID)))) {
              entity.setLayoutName(propertyMap.get(WIDGETLAYOUT_LAYOUT_NAME_PROPERTY_ID).toString());
            }
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID)))) {
              entity.setSectionName(propertyMap.get(WIDGETLAYOUT_SECTION_NAME_PROPERTY_ID).toString());
            }
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID)))) {
              entity.setDisplayName(propertyMap.get(WIDGETLAYOUT_DISPLAY_NAME_PROPERTY_ID).toString());
            }
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(WIDGETLAYOUT_SCOPE_PROPERTY_ID)))) {
              entity.setScope(propertyMap.get(WIDGETLAYOUT_SCOPE_PROPERTY_ID).toString());
            }

            Set widgetsSet = (LinkedHashSet) propertyMap.get(WIDGETLAYOUT_WIDGETS_PROPERTY_ID);

            //Remove old relations from widget entities
            for (WidgetLayoutUserWidgetEntity widgetLayoutUserWidgetEntity : entity.getListWidgetLayoutUserWidgetEntity()) {
              widgetLayoutUserWidgetEntity.getWidget().getListWidgetLayoutUserWidgetEntity()
                      .remove(widgetLayoutUserWidgetEntity);
            }
            entity.setListWidgetLayoutUserWidgetEntity(new LinkedList<WidgetLayoutUserWidgetEntity>());

            List<WidgetLayoutUserWidgetEntity> widgetLayoutUserWidgetEntityList = new LinkedList<WidgetLayoutUserWidgetEntity>();
            int order = 0;
            for (Object widgetObject : widgetsSet) {
              HashMap<String, Object> widget = (HashMap) widgetObject;
              long id = Integer.parseInt(widget.get("id").toString());
              WidgetEntity widgetEntity = widgetDAO.findById(id);
              if (widgetEntity == null) {
                throw new AmbariException("Widget with id " + widget.get("id").toString() + " does not exists");
              }
              WidgetLayoutUserWidgetEntity widgetLayoutUserWidgetEntity = new WidgetLayoutUserWidgetEntity();

              widgetLayoutUserWidgetEntity.setWidget(widgetEntity);
              widgetLayoutUserWidgetEntity.setWidgetOrder(order++);
              widgetLayoutUserWidgetEntity.setWidgetLayout(entity);
              widgetLayoutUserWidgetEntityList.add(widgetLayoutUserWidgetEntity);
              widgetEntity.getListWidgetLayoutUserWidgetEntity().add(widgetLayoutUserWidgetEntity);
              entity.getListWidgetLayoutUserWidgetEntity().add(widgetLayoutUserWidgetEntity);
            }


            widgetLayoutDAO.mergeWithFlush(entity);
          } finally {
            lock.writeLock().unlock();
          }
        }
        return null;
      }
    });

    return getRequestStatus(null);
  }

  @Override
  public RequestStatus deleteResources(Predicate predicate)
      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
    final Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);

    final List<WidgetLayoutEntity> entitiesToBeRemoved = new ArrayList<WidgetLayoutEntity>();
    for (Map<String, Object> propertyMap : propertyMaps) {
      final Long id;
      try {
        id = Long.parseLong(propertyMap.get(WIDGETLAYOUT_ID_PROPERTY_ID).toString());
      } catch (Exception ex) {
        throw new SystemException("WidgetLayout should have numerical id");
      }

      final WidgetLayoutEntity entity = widgetLayoutDAO.findById(id);
      if (entity == null) {
        throw new NoSuchResourceException("There is no widget layout with id " + id);
      }

      entitiesToBeRemoved.add(entity);
    }

    for (WidgetLayoutEntity entity: entitiesToBeRemoved) {
      if (entity.getListWidgetLayoutUserWidgetEntity() != null) {
        for (WidgetLayoutUserWidgetEntity layoutUserWidgetEntity : entity.getListWidgetLayoutUserWidgetEntity()) {
          if (layoutUserWidgetEntity.getWidget().getListWidgetLayoutUserWidgetEntity() != null) {
            layoutUserWidgetEntity.getWidget().getListWidgetLayoutUserWidgetEntity().remove(layoutUserWidgetEntity);
          }
        }
      }
      widgetLayoutDAO.remove(entity);
    }

    return getRequestStatus(null);
  }

  @Override
  protected Set<String> getPKPropertyIds() {
    return pkPropertyIds;
  }

  private String getUserName(Map<String, Object> properties) {
    if (properties.get(WIDGETLAYOUT_USERNAME_PROPERTY_ID) != null) {
      return properties.get(WIDGETLAYOUT_USERNAME_PROPERTY_ID).toString();
    }
    return getManagementController().getAuthName();
  }
}
