package dareka.processor.impl;

import java.util.Objects;

public class VideoDescriptor implements Comparable<VideoDescriptor> {

    private final String id;      // smXXXXXXX
    private final String type;       // sm
    private final String idNumber;   // XXXXXXX
    private final String postfix; // .mp4
    private final boolean dmc;
    // for classic
    private final boolean low; // economy
    // for dmc
    private final String videoMode;
    private final int videoBitrate;
    private final int audioBitrate;
    private final String shortenSrcId; // only for tmp file

    private VideoDescriptor(String id, String postfix, boolean dmc, boolean low,
            String videoMode, int videoBitrate, int audioBitrate, String srcId) {
        this.id = id;
        this.type = id.substring(0, 2);
        this.idNumber = id.substring(2);
        this.postfix = postfix;
        this.dmc = dmc;
        this.low = low;
        if (dmc) {
            this.videoMode = videoMode;
            this.videoBitrate = videoBitrate;
            this.audioBitrate = audioBitrate;
            this.shortenSrcId = srcId.substring(0, Math.min(srcId.length(), 8));
        } else {
            this.videoMode = null;
            this.videoBitrate = 0;
            this.audioBitrate = 0;
            this.shortenSrcId = null;
        }
    }

    public static VideoDescriptor newClassic(String id, String postfix, boolean low) {
        return new VideoDescriptor(id, postfix, false, low, null, 0, 0, null);
    }

    public static VideoDescriptor newDmc(String id, String postfix, boolean low,
            String videoMode, int videoBitrate, int audioBitrate, String srcId) {
        return new VideoDescriptor(id, postfix, true, low, videoMode, videoBitrate, audioBitrate, srcId);
    }

    public String getId() {
        return id;
    }
    
    public String getType() {
        return type;
    }
    
    public String getIdNumber() {
        return idNumber;
    }

    public String getPostfix() {
        return postfix;
    }

    public boolean isDmc() {
        return dmc;
    }

    public boolean isLow() {
        return low;
    }

    public String getVideoMode() {
        return videoMode;
    }

    public int getVideoBitrate() {
        return videoBitrate;
    }

    public int getAudioBitrate() {
        return audioBitrate;
    }

    public String getShortenSrcId() {
        return shortenSrcId;
    }

    public String guessVideoType() {
        return "archive_h264_" + videoBitrate + "_" + videoMode;
    }

    public String guessAudioType() {
        return "archive_aac_" + audioBitrate;
    }
    
    public VideoDescriptor replaceId(String id) {
        return new VideoDescriptor(id, postfix, dmc, low, videoMode, videoBitrate, audioBitrate, shortenSrcId);
    }
    
    public VideoDescriptor replacePostfix(String postfix) {
        return new VideoDescriptor(id, postfix, dmc, low, videoMode, videoBitrate, audioBitrate, shortenSrcId);
    }
    
    public VideoDescriptor replaceLow(boolean low) {
        return new VideoDescriptor(id, postfix, dmc, low, videoMode, videoBitrate, audioBitrate, shortenSrcId);
    }

    public VideoDescriptor stripSrcId() {
        return new VideoDescriptor(id, postfix, dmc, low, videoMode, videoBitrate, audioBitrate, "");
    }

    private int postfixPriority = 0;

    private int getPostfixPriority() {
        if (postfixPriority != 0) {
            return postfixPriority;
        }
        if (postfix == null) {
            return 0;
        }
        if (dmc) {
            if (postfix.equals(Cache.MP4)) {
                postfixPriority = 2;
            } else if (postfix.equals(Cache.FLV)) {
                postfixPriority = 1;
            } else {
                postfixPriority = Integer.MIN_VALUE;
            }
        } else {
            postfixPriority = 0;
        }
        return postfixPriority;
    }

    /**
     * (dmc, postfix)の状況の下でthisがotherより適しているか．
     * postfixが非nullの時はそのpostfixの優先順位を最高とする．
     *
     * DMC:
     *  1. postfix (mp4要求時にflvは返せない)
     *  2. videoBitrate
     *  3. audioBitrate
     *  4. postfix
     * Classic:
     *  1. economy
     * 
     * @param other
     * @param dmc
     * @param postfix null可
     */
    protected boolean isPreferredThan(VideoDescriptor other, boolean dmc, String postfix) {
        if (dmc) {
            if (!this.dmc || Cache.MP4.equals(postfix) && !this.postfix.equals(postfix)) {
                return false;
            }
            if (other == null) {
                return true;
            }
            // 2. videoBitrate
            if (this.videoBitrate < other.videoBitrate) {
                return false;
            } else if (this.videoBitrate > other.videoBitrate) {
                return true;
            }
            // 3. audioBitrate
            if (this.audioBitrate < other.audioBitrate) {
                return false;
            } else if (this.audioBitrate > other.audioBitrate) {
                return true;
            }
            // 4. postfix
            int thisPriority = this.getPostfixPriority();
            int otherPriority = other.getPostfixPriority();
            if (postfix != null) {
                if (this.postfix.equals(postfix)) {
                    thisPriority = Integer.MAX_VALUE;
                }
                if (other.postfix.equals(postfix)) {
                    otherPriority = Integer.MAX_VALUE;
                }
            }
            return thisPriority > otherPriority;
        } else {
            if (this.dmc) {
                return false;
            }
            if (other == null) {
                return true;
            }
            // 1. economy
            if (this.low && !other.low) {
                return false;
            } else if (!this.low && other.low) {
                return true;
            }
            return false;
        }
    }

    /**
     * 半順序上で，より良い動画データかを表す． thisがotherより明らかに良い，つまりthisがあればotherは不要な時にtrueを返す．
     *
     * dmcに対して， - 映像ビットレート - 音声ビットレート - 拡張子 .mp4 > .flv
     * の全てが同じか優れていて，かつ，3点について同じではないときにtrue．
     *
     * 非dmcに対しては， - 非エコノミー動画(!low)が優先される - lowが同じ場合，拡張子間に順序はつかずfalseを返す
     *
     * dmc動画と非dmc動画を比較した場合はfalseを返す．
     *
     * isPreferredThanとの違い: isPreferredThanのほうが全順序に近い． 例えば，flv要求時に300kbps
     * mp4と600kbps flvがあれば 600kbps flvの方がpreferである． 対して，300kbps mp4と600kbps
     * flvの間にどちらがsuperiorかの順序は存在しない．
     */
    protected boolean isSuperiorThan(VideoDescriptor other) {
        if (other == null) {
            return true;
        }
        if (this.dmc != other.dmc) {
            return false;
        }
        if (this.dmc) {
            if (this.videoBitrate < other.videoBitrate
                    || this.audioBitrate < other.audioBitrate
                    || this.getPostfixPriority() < other.getPostfixPriority()) {
                return false;
            }
            if (this.videoBitrate == other.videoBitrate
                    && this.audioBitrate == other.audioBitrate
                    && this.getPostfixPriority() == other.getPostfixPriority()) {
                return false;
            }
            return true;
        } else {
            if (this.low && !other.low) {
                return false;
            } else if (!this.low && other.low) {
                return true;
            }
            return false;
        }
    }

    @Override
    public String toString() {
        if (isDmc()) {
            return getId() + (isLow() ? "low" : "")
                    + "[" + getVideoMode() + "," + getVideoBitrate() + "," + getAudioBitrate() + "]"
                    + getShortenSrcId() + getPostfix();
        } else {
            return getId() + (isLow() ? "low" : "") + getPostfix();
        }
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 79 * hash + Objects.hashCode(this.id);
        hash = 79 * hash + (this.dmc ? 1 : 0);
        if (this.dmc) {
            hash = 79 * hash + Objects.hashCode(this.videoMode);
            hash = 79 * hash + this.videoBitrate;
            hash = 79 * hash + this.audioBitrate;
            hash = 79 * hash + Objects.hashCode(this.shortenSrcId);
            hash = 79 * hash + Objects.hashCode(this.postfix);
        } else {
            hash = 79 * hash + (this.low ? 1 : 0);
        }
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final VideoDescriptor other = (VideoDescriptor) obj;
        if (this.dmc != other.dmc) {
            return false;
        }
        if (dmc) {
            if (this.videoBitrate != other.videoBitrate) {
                return false;
            }
            if (this.audioBitrate != other.audioBitrate) {
                return false;
            }
            if (!Objects.equals(this.shortenSrcId, other.shortenSrcId)) {
                return false;
            }
            if (!Objects.equals(this.videoMode, other.videoMode)) {
                return false;
            }
        } else {
            // dmcに対してはlowは同値性の判定に含めない
            if (this.low != other.low) {
                return false;
            }
        }
        if (!Objects.equals(this.id, other.id)) {
            return false;
        }
        // 非dmcに対してはpostfixは同値性の判定に含めない
        if (this.dmc && !Objects.equals(this.postfix, other.postfix)) {
            return false;
        }
        return true;
    }

    @Override
    public int compareTo(VideoDescriptor o) {
        int t;
        int n1 = Integer.parseInt(this.getIdNumber());
        int n2 = Integer.parseInt(o.getIdNumber());
        if (n1 < n2) {
            return -1;
        } else if (n1 > n2) {
            return 1;
        }
        
        // dmc動画のほうが前(小さい)
        if (dmc && !o.dmc) {
            return -1;
        } else if (!dmc && o.dmc) {
            return 1;
        }
        
        if (dmc) {
            // ビットレートが大きい方が前(小さい)
            if (videoBitrate < o.videoBitrate) {
                return 1;
            } else if (videoBitrate > o.videoBitrate) {
                return -1;
            }
            
            // 自然順序付けすべきだけどビットレートが同じならvideoModeも同じはず
            t = videoMode.compareTo(o.videoMode);
            if (t != 0) return t;
            
            if (audioBitrate < o.audioBitrate) {
                return 1;
            } else if (audioBitrate > o.audioBitrate) {
                return -1;
            }
            
            t = shortenSrcId.compareTo(o.shortenSrcId);
            if (t != 0) return t;
            
            // postfixは優先度が高いものが先
            int tp = this.getPostfixPriority();
            int op = o.getPostfixPriority();
            if (tp > op) {
                return -1;
            } else if (tp < op) {
                return 1;
            }
            // もし同じpriorityを返した時にはnullを末尾とした辞書順
            if (postfix == null && o.postfix == null) return 0;
            if (postfix == null) return 1;
            if (o.postfix == null) return -1;
            t = postfix.compareTo(o.postfix);
            if (t != 0) return t;
        } else {
            // lowが後(大きい)
            if (!low && o.low) {
                return -1;
            } else if (low && !o.low) {
                return 1;
            }
        }
        
        return 0;
    }
}
