// 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.cloudstack.storage.datastore;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.engine.subsystem.api.storage.disktype.DiskFormat;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.log4j.Logger;

import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StoragePoolHostVO;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.storage.encoding.EncodingType;

@SuppressWarnings("serial")
public class PrimaryDataStoreImpl implements PrimaryDataStore {
    private static final Logger s_logger = Logger.getLogger(PrimaryDataStoreImpl.class);

    protected PrimaryDataStoreDriver driver;
    protected StoragePoolVO pdsv;
    @Inject
    protected PrimaryDataStoreDao dataStoreDao;
    protected PrimaryDataStoreLifeCycle lifeCycle;
    @Inject
    private ObjectInDataStoreManager objectInStoreMgr;
    @Inject
    TemplateDataFactory imageDataFactory;
    @Inject
    SnapshotDataFactory snapshotFactory;
    protected DataStoreProvider provider;
    @Inject
    VMTemplatePoolDao templatePoolDao;
    @Inject
    StoragePoolHostDao poolHostDao;

    @Inject
    private VolumeDao volumeDao;
    private Map<String, String> _details;

    public PrimaryDataStoreImpl() {

    }

    public void configure(StoragePoolVO pdsv, PrimaryDataStoreDriver driver, DataStoreProvider provider) {
        this.pdsv = pdsv;
        this.driver = driver;
        this.provider = provider;
    }

    public static PrimaryDataStoreImpl createDataStore(StoragePoolVO pdsv, PrimaryDataStoreDriver driver, DataStoreProvider provider) {
        PrimaryDataStoreImpl dataStore = ComponentContext.inject(PrimaryDataStoreImpl.class);
        dataStore.configure(pdsv, driver, provider);
        return dataStore;
    }

    @Override
    public VolumeInfo getVolume(long id) {
        VolumeVO volumeVO = volumeDao.findById(id);
        VolumeObject vol = VolumeObject.getVolumeObject(this, volumeVO);
        return vol;
    }

    @Override
    public List<VolumeInfo> getVolumes() {
        List<VolumeVO> volumes = volumeDao.findByPoolId(getId());
        List<VolumeInfo> volumeInfos = new ArrayList<VolumeInfo>();
        for (VolumeVO volume : volumes) {
            volumeInfos.add(VolumeObject.getVolumeObject(this, volume));
        }
        return volumeInfos;
    }

    @Override
    public DataStoreDriver getDriver() {
        return driver;
    }

    @Override
    public DataStoreRole getRole() {
        return DataStoreRole.Primary;
    }

    @Override
    public long getId() {
        return pdsv.getId();
    }

    @Override
    public void setDetails(Map<String, String> details) {
        _details = details;
    }

    @Override
    public Map<String, String> getDetails() {
        return _details;
    }

    @Override
    public String getUri() {
        String path = pdsv.getPath().replaceFirst("/*", "");
        StringBuilder builder = new StringBuilder();
        builder.append(pdsv.getPoolType());
        builder.append("://");
        builder.append(pdsv.getHostAddress());
        builder.append(File.separator);
        builder.append(path);
        builder.append(File.separator);
        builder.append("?" + EncodingType.ROLE + "=" + getRole());
        builder.append("&" + EncodingType.STOREUUID + "=" + pdsv.getUuid());
        return builder.toString();
    }

    @Override
    public Scope getScope() {
        StoragePoolVO vo = dataStoreDao.findById(pdsv.getId());
        if (vo.getScope() == ScopeType.CLUSTER) {
            return new ClusterScope(vo.getClusterId(), vo.getPodId(), vo.getDataCenterId());
        } else if (vo.getScope() == ScopeType.ZONE) {
            return new ZoneScope(vo.getDataCenterId());
        } else if (vo.getScope() == ScopeType.HOST) {
            List<StoragePoolHostVO> poolHosts = poolHostDao.listByPoolId(vo.getId());
            if (poolHosts.size() > 0) {
                return new HostScope(poolHosts.get(0).getHostId(), vo.getClusterId(), vo.getDataCenterId());
            }
            s_logger.debug("can't find a local storage in pool host table: " + vo.getId());
        }
        return null;
    }

    @Override
    public boolean isHypervisorSupported(HypervisorType hypervisor) {
        return true;
    }

    @Override
    public boolean isLocalStorageSupported() {
        return false;
    }

    @Override
    public boolean isVolumeDiskTypeSupported(DiskFormat diskType) {
        return false;
    }

    @Override
    public String getUuid() {
        return pdsv.getUuid();
    }

    @Override
    public String getName() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public PrimaryDataStoreLifeCycle getLifeCycle() {
        return lifeCycle;
    }

    @Override
    public boolean exists(DataObject data) {
        return (objectInStoreMgr.findObject(data, data.getDataStore()) != null) ? true : false;
    }

    @Override
    public TemplateInfo getTemplate(long templateId) {
        VMTemplateStoragePoolVO template = templatePoolDao.findByPoolTemplate(getId(), templateId);
        if (template == null || template.getState() != ObjectInDataStoreStateMachine.State.Ready) {
            return null;
        }
        return imageDataFactory.getTemplate(templateId, this);
    }

    @Override
    public SnapshotInfo getSnapshot(long snapshotId) {
        return null;
    }

    @Override
    public DiskFormat getDefaultDiskType() {
        return null;
    }

    @Override
    public boolean isManaged() {
        return pdsv.isManaged();
    }

    private boolean canCloneVolume() {
        return Boolean.valueOf(getDriver().getCapabilities().get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString()));
    }

    /**
     * The parameter createEntryInTempSpoolRef in the overloaded create(DataObject, boolean) method only applies to managed storage. We pass
     * in "true" here.
     *
     * In the case of managed storage that can create a volume from a volume (clone), if the DataObject passed in is a TemplateInfo,
     * we do want to create an entry in the cloud.template_spool_ref table (so that multiple uses of the template can be leveraged from
     * the one copy on managed storage).
     *
     * In cases where UUID resigning is not available, then the code calling "create" should invoke the overloaded "create" method whose second
     * parameter is a boolean. This code can pass in "false" so that an entry in the cloud.template_spool_ref table is not created (no template to share
     * on the primary storage).
     */
    @Override
    public DataObject create(DataObject dataObject) {
        return create(dataObject, true);
    }

    /**
     * Please read the comment for the create(DataObject) method if you are planning on passing in "false" for createEntryInTempSpoolRef.
     */
    @Override
    public DataObject create(DataObject obj, boolean createEntryInTempSpoolRef) {
        // create template on primary storage
        if (obj.getType() == DataObjectType.TEMPLATE && (!isManaged() || (createEntryInTempSpoolRef && canCloneVolume()))) {
            try {
                String templateIdPoolIdString = "templateId:" + obj.getId() + "poolId:" + getId();
                VMTemplateStoragePoolVO templateStoragePoolRef;
                GlobalLock lock = GlobalLock.getInternLock(templateIdPoolIdString);
                if (!lock.lock(5)) {
                    s_logger.debug("Couldn't lock the db on the string " + templateIdPoolIdString);
                    return null;
                }
                try {
                    templateStoragePoolRef = templatePoolDao.findByPoolTemplate(getId(), obj.getId());
                    if (templateStoragePoolRef == null) {

                        if (s_logger.isDebugEnabled()) {
                            s_logger.debug("Not found (" + templateIdPoolIdString + ") in template_spool_ref, persisting it");
                        }
                        templateStoragePoolRef = new VMTemplateStoragePoolVO(getId(), obj.getId());
                        templateStoragePoolRef = templatePoolDao.persist(templateStoragePoolRef);
                    }
                } catch (Throwable t) {
                    if (s_logger.isDebugEnabled()) {
                        s_logger.debug("Failed to insert (" + templateIdPoolIdString + ") to template_spool_ref", t);
                    }
                    templateStoragePoolRef = templatePoolDao.findByPoolTemplate(getId(), obj.getId());
                    if (templateStoragePoolRef == null) {
                        throw new CloudRuntimeException("Failed to create template storage pool entry");
                    } else {
                        if (s_logger.isDebugEnabled()) {
                            s_logger.debug("Another thread already inserts " + templateStoragePoolRef.getId() + " to template_spool_ref", t);
                        }
                    }
                } finally {
                    lock.unlock();
                    lock.releaseRef();
                }
            } catch (Exception e) {
                s_logger.debug("Caught exception ", e);
            }
        } else if (obj.getType() == DataObjectType.SNAPSHOT) {
            return objectInStoreMgr.create(obj, this);
        } else if (obj.getType() == DataObjectType.VOLUME) {
            VolumeVO vol = volumeDao.findById(obj.getId());
            if (vol != null) {
                vol.setPoolId(getId());
                volumeDao.update(vol.getId(), vol);
            }
        }

        return objectInStoreMgr.get(obj, this);
    }

    @Override
    public boolean delete(DataObject obj) {
        //TODO: clean up through driver
        objectInStoreMgr.delete(obj);
        return true;
    }

    @Override
    public long getDataCenterId() {
        return pdsv.getDataCenterId();
    }

    @Override
    public String getPath() {
        return pdsv.getPath();
    }

    @Override
    public StoragePoolType getPoolType() {
        return pdsv.getPoolType();
    }

    @Override
    public Date getCreated() {
        return pdsv.getCreated();
    }

    @Override
    public Date getUpdateTime() {
        return pdsv.getUpdateTime();
    }

    @Override
    public long getCapacityBytes() {
        return pdsv.getCapacityBytes();
    }

    @Override
    public long getUsedBytes() {
        return pdsv.getUsedBytes();
    }

    @Override
    public Long getCapacityIops() {
        return pdsv.getCapacityIops();
    }

    @Override
    public Long getClusterId() {
        return pdsv.getClusterId();
    }

    @Override
    public String getHostAddress() {
        return pdsv.getHostAddress();
    }

    @Override
    public String getUserInfo() {
        return pdsv.getUserInfo();
    }

    @Override
    public boolean isShared() {
        return pdsv.getScope() == ScopeType.HOST ? false : true;
    }

    @Override
    public boolean isLocal() {
        return !isShared();
    }

    @Override
    public StoragePoolStatus getStatus() {
        return pdsv.getStatus();
    }

    @Override
    public int getPort() {
        return pdsv.getPort();
    }

    @Override
    public Long getPodId() {
        return pdsv.getPodId();
    }

    public Date getRemoved() {
        return pdsv.getRemoved();
    }

    @Override
    public boolean isInMaintenance() {
        return getStatus() == StoragePoolStatus.PrepareForMaintenance || getStatus() == StoragePoolStatus.Maintenance ||
            getStatus() == StoragePoolStatus.ErrorInMaintenance || getRemoved() != null;
    }

    @Override
    public HypervisorType getHypervisor() {
       return pdsv.getHypervisor();
    }

    @Override
    public String getStorageProviderName() {
        return pdsv.getStorageProviderName();
    }

    @Override
    public DataStoreTO getTO() {
        DataStoreTO to = getDriver().getStoreTO(this);
        if (to == null) {
            PrimaryDataStoreTO primaryTO = new PrimaryDataStoreTO(this);
            return primaryTO;
        }
        return to;
    }
}
