package com.devicehive.dao.rdbms;

/*
 * #%L
 * DeviceHive Dao RDBMS Implementation
 * %%
 * Copyright (C) 2016 DataArt
 * %%
 * Licensed 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.
 * #L%
 */

import com.devicehive.auth.HivePrincipal;
import com.devicehive.dao.DeviceDao;
import com.devicehive.model.Device;
import com.devicehive.model.DeviceClass;
import com.devicehive.model.Network;
import com.devicehive.vo.DeviceVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static java.util.Optional.of;
import static java.util.Optional.ofNullable;

@Repository
public class DeviceDaoRdbmsImpl extends RdbmsGenericDao implements DeviceDao {
    private static final String GET_DEVICES_GUIDS_AND_OFFLINE_TIMEOUT = "SELECT d.guid, dc.offline_timeout FROM device d " +
                                                                        "LEFT JOIN device_class dc " +
                                                                        "ON dc.id = d.device_class_id " +
                                                                        "WHERE d.guid IN (:guids)";
    private static final String UPDATE_DEVICES_STATUSES = "UPDATE device SET status =:status WHERE guid IN (:guids)";

    private NamedParameterJdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    /**
     * Method change statuses for devices with guids that consists in the list
     *
     * @param status new status
     * @param guids  list of guids
     */
    public void changeStatusForDevices(String status, List<String> guids) {
        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("status", status);
        parameters.addValue("guids", guids);
        jdbcTemplate.update(UPDATE_DEVICES_STATUSES, parameters);
    }

    /**
     * Method return a Map where KEY is a device guid from guids list and
     * VALUE is OfflineTimeout from deviceClass for device with current guid.
     *
     * @param guids list of guids
     */
    public Map<String, Integer> getOfflineTimeForDevices(List<String> guids) {
        final Map<String, Integer> deviceInfo = new HashMap<>();
        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("guids", guids);
        List<Map<String, Object>> results = jdbcTemplate.queryForList(GET_DEVICES_GUIDS_AND_OFFLINE_TIMEOUT, parameters);
        results.stream().forEach(map -> deviceInfo.put((String) map.get("guid"), (Integer) map.get("offline_timeout")));
        return deviceInfo;
    }


    @Override
    public DeviceVO findByUUID(String uuid) {
        Device deviceEntity = createNamedQuery(Device.class, "Device.findByUUID", Optional.of(CacheConfig.refresh()))
                .setParameter("guid", uuid)
                .getResultList()
                .stream().findFirst().orElse(null);
        return Device.convertToVo(deviceEntity);
    }

    @Override
    public void persist(DeviceVO vo) {
        Device device = Device.convertToEntity(vo);
        device.setDeviceClass(reference(DeviceClass.class, device.getDeviceClass().getId()));
        if (device.getNetwork() != null) {
            device.setNetwork(reference(Network.class, device.getNetwork().getId()));
        }
        super.persist(device);
        vo.setId(device.getId());
    }


    @Override
    public DeviceVO merge(DeviceVO vo) {
        Device device = Device.convertToEntity(vo);
        device.setDeviceClass(reference(DeviceClass.class, device.getDeviceClass().getId()));
        if (device.getNetwork() != null) {
            device.setNetwork(reference(Network.class, device.getNetwork().getId()));
        }
        Device merged = super.merge(device);
        return Device.convertToVo(merged);
    }

    @Override
    public int deleteByUUID(String guid) {
        return createNamedQuery("Device.deleteByUUID", Optional.<CacheConfig>empty())
                .setParameter("guid", guid)
                .executeUpdate();
    }

    @Override
    public List<DeviceVO> getDeviceList(List<String> guids, HivePrincipal principal) {
        final CriteriaBuilder cb = criteriaBuilder();
        final CriteriaQuery<Device> criteria = cb.createQuery(Device.class);
        final Root<Device> from = criteria.from(Device.class);
        final Predicate[] predicates = CriteriaHelper.deviceListPredicates(cb, from, guids, Optional.ofNullable(principal));
        criteria.where(predicates);
        final TypedQuery<Device> query = createQuery(criteria);
        CacheHelper.cacheable(query);
        return query.getResultList().stream().map(Device::convertToVo).collect(Collectors.toList());
    }

    @Override
    public long getAllowedDeviceCount(HivePrincipal principal, List<String> guids) {
        final CriteriaBuilder cb = criteriaBuilder();
        final CriteriaQuery<Device> criteria = cb.createQuery(Device.class);
        final Root<Device> from = criteria.from(Device.class);
        final Predicate[] predicates = CriteriaHelper.deviceListPredicates(cb, from, guids, Optional.ofNullable(principal));
        criteria.where(predicates);
        final TypedQuery<Device> query = createQuery(criteria);
        return query.getResultList().size();
    }

    @Override
    public List<DeviceVO> list(String name, String namePattern, Long networkId, String networkName,
                                Long deviceClassId, String deviceClassName, String sortField, @NotNull boolean sortOrderAsc, Integer take,
                                Integer skip, HivePrincipal principal) {
        final CriteriaBuilder cb = criteriaBuilder();
        final CriteriaQuery<Device> criteria = cb.createQuery(Device.class);
        final Root<Device> from = criteria.from(Device.class);

        final Predicate [] predicates = CriteriaHelper.deviceListPredicates(cb, from, ofNullable(name), ofNullable(namePattern), ofNullable(networkId), ofNullable(networkName),
                ofNullable(deviceClassId), ofNullable(deviceClassName), ofNullable(principal));

        criteria.where(predicates);
        CriteriaHelper.order(cb, criteria, from, ofNullable(sortField), sortOrderAsc);

        final TypedQuery<Device> query = createQuery(criteria);
        cacheQuery(query, of(CacheConfig.refresh()));
        ofNullable(take).ifPresent(query::setMaxResults);
        ofNullable(skip).ifPresent(query::setFirstResult);
        List<Device> resultList = query.getResultList();
        return resultList.stream().map(Device::convertToVo).collect(Collectors.toList());
    }
}

@@ -6648,17 +6648,8 @@
 eld,
- @NotNull
  boo

Migrate sortOrder param from Boolean to boolean