package saccubus.worker.impl.download;

import static saccubus.worker.impl.download.DownloadStatus.*;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.EnumSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nicobrowser.DownloadCommentType;
import nicobrowser.GetFlvResult;
import nicobrowser.NamePattern;
import nicobrowser.NicoHttpClient;
import nicobrowser.ProgressListener;
import nicobrowser.WayBackInfo;
import nicobrowser.entity.NicoContent.Status;
import saccubus.worker.Worker;
import saccubus.worker.WorkerListener;
import saccubus.worker.profile.CommentProfile;
import saccubus.worker.profile.GeneralProfile;
import saccubus.worker.profile.DownloadProfile;
import saccubus.worker.profile.ProxyProfile;

/**
 * <p>タイトル: さきゅばす</p>
 *
 * <p>説明: ニコニコ動画の動画をコメントつきで保存</p>
 *
 * <p>著作権: Copyright (c) 2007 PSI</p>
 *
 * <p>会社名: </p>
 *
 * @author 未入力
 * @version 1.0
 */
public class Download extends Worker<DownloadResult, DownloadProgress> {

    private static final Logger logger = LoggerFactory.getLogger(Download.class);
    private static final Object timeLockObj = new Object();
    private static volatile long lastStartTime = 0L;
    private final DownloadProfile profile;
    private final String videoId;
    private final int waitDownload;

    public Download(DownloadProfile profile, String videoId) {
        this(profile, videoId, null, 30);
    }

    /**
     * コンバータを構築します.
     * @param videoId 対象となる動画のID.
     * @param time
     * @param profile
     * @param listener
     * @param flag
     */
    public Download(DownloadProfile profile, String videoId, WorkerListener<DownloadResult, DownloadProgress> listener,
            int wait) {
        super(listener);
        this.videoId = videoId;
        this.profile = profile;
        this.waitDownload = wait;
    }

    @Override
    public DownloadResult work() throws Exception {
        waitAndGo();

        publish(new DownloadProgress(PROCESS, 0.0, "ログイン中"));

        NicoHttpClient client = null;
        nicobrowser.VideoInfo vi = null;
        NamePattern videoNamePattern = null;
        WayBackInfo wbi = null;
        if (needsLogin()) {
            client = createClientAndLogin();
            vi = client.getVideoInfo(videoId);

            final String name = profile.getVideoProfile().getFileName();
            videoNamePattern = new NamePattern(name, vi.getTitleInWatchPage());

            if (needsBackLog()) {
                final String key = client.getWayBackKey(vi);
                wbi = new WayBackInfo(key, profile.getCommentProfile().getBackLogPoint());
            }
        }

        checkStop();

        File commentFile;
        if (profile.getCommentProfile().isDownload()) {
            final CommentProfile prof = profile.getCommentProfile();
            final GeneralProfile gene = profile.getGeneralProfile();

            final NamePattern pattern = new NamePattern(prof.getFileName(), vi.getTitleInWatchPage());
            final String name = pattern.createFileName(videoId, true);
            final File file = new File(profile.getCommentProfile().getDir(), name);

            final EnumSet<DownloadCommentType> commentSet = EnumSet.of(DownloadCommentType.OWNER);
            if (!profile.getCommentProfile().isOwnerCommentOnly()) {
                if (profile.getCommentProfile().isDisablePerMinComment()) {
                    commentSet.add(DownloadCommentType.COMMENT_OLD);
                } else {
                    commentSet.add(DownloadCommentType.COMMENT);
                }
            }
            commentFile = client.getCommentFile(vi, file.getPath(), commentSet, wbi, profile.getCommentProfile().
                    getLengthRelatedCommentSize());
        } else {
            commentFile = profile.getCommentProfile().getLocalFile();
        }

        checkStop();

        File videoFile;
        GetFlvResult vf;
        if (profile.getVideoProfile().isDownload()) {
            vf = client.getFlvFile(vi, profile.getVideoProfile().getDir(), videoNamePattern,
                    Status.GET_INFO, true, new ProgressListener() {

                @Override
                public void progress(long fileSize, long downloadSize) {
                    final double vol = (double) downloadSize / (double) fileSize * 100.0;
                    publish(new DownloadProgress(PROCESS, vol, String.format("ダウンロード%.2f%%", vol)));
                }
            });

            videoFile = vf.getFile();
        } else {
            videoFile = profile.getVideoProfile().getLocalFile();
        }

        return new DownloadResult(true, videoFile, commentFile);
    }

    /** @return 何かダウンロードするものがあればtrue. */
    private static boolean needsDownload(DownloadProfile profile) {
        return (profile.getVideoProfile().isDownload() || profile.getCommentProfile().isDownload());
    }

    /**
     * HttpClientを生成し, ニコニコ動画サーバへログインします.
     * @return 生成したHttpClientインスタンス.
     * @throws IOException ログイン失敗.
     * @throws InterruptedException ログイン失敗.
     */
    private NicoHttpClient createClientAndLogin() throws IOException, InterruptedException {
        final NicoHttpClient client = createClient(profile.getProxyProfile());
        if (profile.getLoginProfile().needsLogin()) {

            final boolean hasLogin = client.login(profile.getLoginProfile().getMail(), profile.getLoginProfile().
                    getPassword());
            if (!hasLogin) {
                throw new IOException("login failed");
            }
        } else {
            client.addCookie(profile.getLoginProfile().getCookies());
        }

        boolean hasLogin = client.challengeAuth();
        if (!hasLogin) {
            throw new IOException("invalid session(wrong id/pass or cookie parameter)");
        }

        return client;
    }

    private NicoHttpClient createClient(ProxyProfile proxy) {
        if (proxy.use()) {
            return new NicoHttpClient(proxy.getHost(), proxy.getPort());
        } else {
            return new NicoHttpClient();
        }
    }

    /** @return ログインする必要があればtrue. */
    private boolean needsLogin() {
        return profile.getVideoProfile().isDownload() || profile.getCommentProfile().isDownload();
    }

    /** @return 過去ログ取得の必要があればtrue. */
    private boolean needsBackLog() {
        return profile.getCommentProfile().getBackLogPoint() >= 0L;
    }

    private void checkStop() throws InterruptedException {
        if (Thread.interrupted()) {
            throw new InterruptedException("中止要求を受け付けました");
        }
    }

    /**
     * ニコニコ動画サービスに連続アクセスするとはじかれるため, 前回のアクセス開始時刻から一定期間は
     * 次のアクセスに行かないよう待機する必要があります.
     * @throws InterruptedException 中断されました.
     */
    private void waitAndGo() throws InterruptedException {
        synchronized (timeLockObj) {
            final long now = new Date().getTime();
            final long needSleep = (waitDownload * 1000L) - (now - lastStartTime);
            if (needSleep > 0L) {
                publish(new DownloadProgress(DownloadStatus.PROCESS, -1.0, "過剰アクセス抑制待機 " + needSleep / 1000));
                Thread.sleep(needSleep);
            }
            lastStartTime = new Date().getTime();
        }
    }
}
