/*
 * Copyright (C) 2010-2101 Alibaba Group Holding Limited.
 *
 * 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.
 */

package com.alibaba.otter.node.etl.common.pipe.impl.http;

import java.io.File;
import java.io.InputStream;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

import com.alibaba.otter.node.etl.OtterConstants;
import com.alibaba.otter.node.etl.common.io.EncryptedData;
import com.alibaba.otter.node.etl.common.io.download.DataRetriever;
import com.alibaba.otter.node.etl.common.pipe.PipeDataType;
import com.alibaba.otter.node.etl.common.pipe.exception.PipeException;
import com.alibaba.otter.node.etl.common.pipe.impl.http.archive.ArchiveBean;
import com.alibaba.otter.node.etl.common.pipe.impl.http.archive.ArchiveRetriverCallback;
import com.alibaba.otter.node.etl.common.pipe.impl.http.archive.LazyFileInputStream;
import com.alibaba.otter.node.etl.load.loader.db.FileloadDumper;
import com.alibaba.otter.shared.common.model.config.pipeline.Pipeline;
import com.alibaba.otter.shared.etl.model.FileBatch;
import com.alibaba.otter.shared.etl.model.FileData;
import com.alibaba.otter.shared.etl.model.Identity;

/**
 * 基于文件附件的http协议的管道
 * 
 * @author jianghang 2011-10-17 下午03:11:44
 * @version 4.0.0
 */
public class AttachmentHttpPipe extends AbstractHttpPipe<Object, HttpPipeKey> implements BeanFactoryAware {

    private static final Logger logger  = LoggerFactory.getLogger(AttachmentHttpPipe.class);
    private BeanFactory         beanFactory;
    private boolean             encrypt = false;

    @Override
    public HttpPipeKey put(Object data) throws PipeException {
        if (data instanceof FileBatch) {
            return archiveFile((FileBatch) data);
        } else {
            throw new IllegalArgumentException("error argument");
        }
    }

    public File get(HttpPipeKey key) throws PipeException {
        return unpackFile(key);
    }

    // 处理对应的附件
    private HttpPipeKey archiveFile(final FileBatch fileBatch) {
        // 处理构造对应的文件url
        String filename = buildFileName(fileBatch.getIdentity(), ClassUtils.getShortClassName(fileBatch.getClass()));
        File file = new File(htdocsDir, filename);
        // 压缩对应的文件数据
        List<FileData> fileDatas = fileBatch.getFiles();
        Pipeline pipeline = configClientService.findPipeline(fileBatch.getIdentity().getPipelineId());
        int poolSize = pipeline.getParameters().getFileLoadPoolSize();
        boolean useLocalFileMutliThread = pipeline.getParameters().getUseLocalFileMutliThread();
        ArchiveBean archiveBean = getArchiveBean();
        archiveBean.adjustPoolSize(poolSize);// 调整线程池大小
        archiveBean.setUseLocalFileMutliThread(useLocalFileMutliThread);// 设置是否启用local多线程同步
        boolean done = archiveBean.pack(file, fileDatas, new ArchiveRetriverCallback<FileData>() {

            public InputStream retrive(FileData fileData) {
                boolean miss = false;
                try {
                    if (StringUtils.isNotEmpty(fileData.getNameSpace())) {
                        throw new RuntimeException(fileData + " is not support!");
                    } else {
                        File source = new File(fileData.getPath());
                        if (source.exists() && source.isFile()) {
                            return new LazyFileInputStream(source);
                        } else {
                            miss = true;
                            return null;
                        }
                    }
                } finally {
                    if (miss && logger.isInfoEnabled()) {
                        MDC.put(OtterConstants.splitPipelineLoadLogFileKey,
                                String.valueOf(fileBatch.getIdentity().getPipelineId()));
                        logger.info(FileloadDumper.dumpMissFileDatas(fileBatch.getIdentity(), fileData));
                    }
                }

            }
        });

        if (done == false) {
            return null; // 直接返回
        }

        HttpPipeKey key = new HttpPipeKey();
        key.setUrl(remoteUrlBuilder.getUrl(fileBatch.getIdentity().getPipelineId(), filename));
        key.setDataType(PipeDataType.FILE_BATCH);
        key.setIdentity(fileBatch.getIdentity());
        if (encrypt || pipeline.getParameters().getUseFileEncrypt()) {
            // 加密处理
            EncryptedData encryptedData = encryptFile(file);
            key.setKey(encryptedData.getKey());
            key.setCrc(encryptedData.getCrc());
        }
        return key;
    }

    // 处理对应的附件
    private File unpackFile(HttpPipeKey key) {
        Pipeline pipeline = configClientService.findPipeline(key.getIdentity().getPipelineId());
        DataRetriever dataRetriever = dataRetrieverFactory.createRetriever(pipeline.getParameters().getRetriever(),
                                                                           key.getUrl(), downloadDir);
        File archiveFile = null;
        try {
            dataRetriever.connect();
            dataRetriever.doRetrieve();
            archiveFile = dataRetriever.getDataAsFile();
        } catch (Exception e) {
            dataRetriever.abort();
            throw new PipeException("download_error", e);
        } finally {
            dataRetriever.disconnect();
        }

        // 处理下有加密的数据
        if (StringUtils.isNotEmpty(key.getKey()) && StringUtils.isNotEmpty(key.getCrc())) {
            decodeFile(archiveFile, key.getKey(), key.getCrc());
        }

        // 去除末尾的.gzip后缀,做为解压目录
        String dir = StringUtils.removeEnd(archiveFile.getPath(),
                                           FilenameUtils.EXTENSION_SEPARATOR_STR
                                                   + FilenameUtils.getExtension(archiveFile.getPath()));
        File unpackDir = new File(dir);
        // 开始解压
        getArchiveBean().unpack(archiveFile, unpackDir);
        return unpackDir;
    }

    // 构造文件名
    private String buildFileName(Identity identity, String prefix) {
        Date now = new Date();
        String time = new SimpleDateFormat(DATE_FORMAT).format(now);
        return MessageFormat.format("{0}-{1}-{2}-{3}-{4}.gzip", prefix, time, String.valueOf(identity.getChannelId()),
                                    String.valueOf(identity.getPipelineId()), String.valueOf(identity.getProcessId()));
    }

    private ArchiveBean getArchiveBean() {
        // archiveBean做了池化处理,所以每次需要重容器里拿一次
        return (ArchiveBean) beanFactory.getBean("archiveBean", ArchiveBean.class);
    }

    // ================== setter / getter ===================

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void setEncrypt(boolean encrypt) {
        this.encrypt = encrypt;
    }

}