import groovy.json.JsonException
import groovy.json.JsonSlurper
import org.serviio.library.metadata.MediaFileType
import org.serviio.library.online.*
import sun.misc.BASE64Decoder

import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import java.util.regex.Matcher
import java.util.zip.GZIPInputStream

/**
 * WebResource extractor plugin for SeasonVar.ru
 *
 * @author Georgy Baydjanov, Michael Mishalov
 * @version 19.0
 */
class SeasonVar extends WebResourceUrlExtractor {
    final static VALID_WEB_RESOURCE_URL                        = '^(?:http?://)?(?:www\\.)?seasonvar.ru/.*'
    final static JS_SOURCE_REGEX                               =/(?s);eval\((.*?)\);<\/script>/
    final static JS_INNER_SOURCE_REGEX                         =/(?s);eval\((.*?)\);         \$\(/
    final static PLAYLIST_REGEX                                =/(?s)pl[0-9]* =( |)"(.*?)";/
    final static PLAYER_PHP_REGEX                              =/(?s)php",(.*?)\);\$/
    final static PLAYER_PHP_REGEX2                             =/(?s)player.php",(.*?), function/
    final static ID_REGEX                                      =/(?s)var id = "(.*?)";/
    final static SECURE_MARK_REGEX                             =/(?s)var secureMark = "(.*?)";/
    final static TITLE_REGEX                                   = /(?s)<title>(.*?)<\/title>/
    final static SEASON_ID_REGEX                               = /(?s)html5play" idSeas="(.*?)">HTML5/
    final static USER_AGENT                                    = 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0'
    private static final String PLAYER_PHP_URL                 = "http://seasonvar.ru/player.php" /*new*/
    final static codecsMatrix = [codecA:[["b", "U", "N", "I", "Z", "o", "H", "V", "T", "w", "W", "D", "a", "f", "t", "Q", "9", "v", "g", "8", "x", "0", "1", "4", "c", "="],
                                         ["5", "L", "i", "f", "B", "v", "D", "Q", "9", "T", "a", "V", "o", "Y", "w", "J", "7", "M", "Z", "c", "d", "g", "y", "e", "u", "="],
                                         ["Z", "8", "i", "t", "b", "M", "U", "9", "z", "Y", "1", "s", "7", "c", "R", "J", "I", "l", "G", "a", "m", "k", "f", "4", "6", "="],
                                         ["g", "N", "8", "z", "a", "o", "3", "W", "x", "7", "2", "L", "t", "Q", "v", "e", "l", "w", "U", "5", "m", "s", "V", "D", "Y", "="],
                                         ["0", "t", "a", "d", "7", "J", "B", "H", "x", "Q", "y", "Z", "5", "I", "1", "v", "g", "b", "M", "z", "N", "L", "R", "c", "w", "="],
                                         ["W", "a", "u", "V", "d", "M", "8", "6", "7", "i", "H", "Y", "9", "5", "N", "s", "w", "D", "p", "l", "T", "Z", "x", "G", "m", "="],
                                         ["J", "p", "v", "n", "s", "R", "0", "3", "T", "m", "w", "u", "9", "x", "g", "a", "G", "L", "U", "X", "z", "t", "b", "7", "H", "="]],  /*new*/
                                 codecB:[["p", "z", "m", "X", "R", "J", "2", "Y", "d", "n", "B", "G", "u", "y", "L", "s", "l", "M", "i", "e", "6", "k", "7", "3", "5", "F"],
                                         ["s", "p", "6", "3", "N", "G", "8", "k", "0", "U", "t", "z", "H", "l", "x", "W", "m", "n", "R", "I", "2", "b", "X", "1", "4", "E"],
                                         ["d", "X", "x", "n", "2", "3", "v", "5", "L", "u", "D", "g", "H", "N", "W", "y", "w", "V", "B", "e", "o", "Q", "p", "0", "T", "O"],
                                         ["9", "1", "4", "Z", "I", "M", "6", "i", "X", "J", "d", "f", "T", "u", "G", "n", "y", "B", "c", "k", "R", "b", "p", "0", "H", "C"],
                                         ["u", "U", "f", "T", "k", "W", "X", "4", "8", "2", "9", "Y", "o", "D", "e", "n", "l", "s", "3", "G", "V", "p", "i", "m", "6", "F"],
                                         ["2", "B", "g", "v", "Q", "n", "z", "b", "3", "f", "y", "1", "J", "I", "k", "X", "0", "e", "L", "c", "R", "4", "o", "t", "U", "O"],
                                         ["f", "N", "W", "5", "e", "l", "V", "D", "y", "Z", "I", "i", "M", "o", "Q", "1", "B", "8", "2", "6", "c", "d", "4", "Y", "k", "C"]]]
    private static final String SERVER_URI = "http://seasonvar.ru"
    final static String TITLE_CUT = new String(new BASE64Decoder().decodeBuffer("INGB0LzQvtGC0YDQtdGC0Ywg0L7QvdC70LDQudC9"))
    final static String TRANSLATION_PARAM= "translation"
    final static String TRANSLATION_DEFAULT = "trans"
    final static String TRANSLATION_REGEX = /(transLostFilm|transAmedia|transBaibaKo|transAltPro|transHamster|transViruseProject|transAlexFilm|transNewStudio|transTo4kaTV|transColdFilm|trans%D0%A1%D1%83%D0%B1%D1%82%D0%B8%D1%82%D1%80%D1%8B|trans%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%B9|trans)/

    @Override
    protected WebResourceContainer extractItems(URL url, int i) {
        def params = url.query!=null? url.query.split(" ").inject([:]){
            params,
            param-> if(param.contains("=")){
                def (key, value) = param.split('=').toList();
                params[key] = value;
            }
                params
        }:[:];
        List<WebResourceItem> items = []
        String pageSource = openURL(url);
        String playlistURL;
        Matcher jsSource = pageSource=~JS_SOURCE_REGEX
        if(jsSource.size()>0)
            playlistURL = getPlaylistURL((jsSource[0]-null)[-1].toString().trim(), url);
        else{
            playlistURL = getPlaylistURL(getSourceFromPlayer(pageSource,url))
        }
        assert (playlistURL!=null);
        playlistURL = (params[TRANSLATION_PARAM]!=null)?
                playlistURL.replaceAll(TRANSLATION_REGEX, TRANSLATION_DEFAULT+params[TRANSLATION_PARAM]):
                playlistURL.replaceAll(TRANSLATION_REGEX, TRANSLATION_DEFAULT)
        String title= ((pageSource=~TITLE_REGEX)[0]-null)[-1].toString().trim();
        String seasonID= ((pageSource=~SEASON_ID_REGEX)[0]-null)[-1].toString().trim();

        title = title.substring(0,title.indexOf(TITLE_CUT));

        title = title.replaceAll("с","c");
        title = title.replaceAll("С","C");
        title = new String(title.getBytes(),"UTF-8")

        String json = openURL(new URL(SERVER_URI +playlistURL));
        if(!json.endsWith("}"))
            json = decrypt(json);
        def slurper = new JsonSlurper();
        def playlist = slurper.parseText(json) ;
        items.addAll(fetchItems(playlist,seasonID)) ;
        return new WebResourceContainer(title:title, thumbnailUrl: "http://cdn.seasonvar.ru/oblojka/"+seasonID+".jpg", items: items)
    }
    /**
     * Retrieves playlist URL
     * @param obfuscatedStr Obfuscated JavaScript source
     * @return playlist URL
     */
    private String getPlaylistURL(String obfuscatedStr, URL referer){
        obfuscatedStr = "jsSource ="+obfuscatedStr
        ScriptEngineManager manager = new ScriptEngineManager()
        ScriptEngine engine = manager.getEngineByName("JavaScript")
        engine.eval(obfuscatedStr)
        obfuscatedStr = engine.get("jsSource")
        if (obfuscatedStr.contains("flashvars=")){
            return getPlaylistURL(obfuscatedStr)
        }else{
            if(obfuscatedStr.contains("secure")){
                String pageSource = getSourceFromPlayer(obfuscatedStr, referer)
                return getPlaylistURL(((pageSource=~JS_INNER_SOURCE_REGEX)[0]-null)[-1].toString().trim(), referer);
            }
            obfuscatedStr = obfuscatedStr.substring(6,obfuscatedStr.length()-2);
            if(obfuscatedStr.contains("eval")){
                obfuscatedStr = obfuscatedStr.substring(obfuscatedStr.indexOf("eval")+5)
            }
            return getPlaylistURL(obfuscatedStr,referer)
        }
    }
    private getPlaylistURL(String source){
        return decrypt(((source=~PLAYLIST_REGEX)[0]-null)[-1].toString().trim())
    }
    private String getSourceFromPlayer(String string, URL referer) {
        Matcher matcher = string =~ PLAYER_PHP_REGEX
        if(matcher.size()==0)
            matcher = string=~PLAYER_PHP_REGEX2
        def playerLoaderJson = (matcher[0] - null)[-1].toString().trim()
        def slurper = new JsonSlurper();

        def playerLoaderObj = [id:'', secure:''];
        try {
            playerLoaderObj = slurper.parseText(playerLoaderJson);
        }catch (JsonException ignored){
            playerLoaderObj.id = (((string=~ID_REGEX)[0]) - null)[-1].toString().trim()
            playerLoaderObj.secure = (((string=~SECURE_MARK_REGEX)[0]) - null)[-1].toString().trim()
        }

        postToURL(referer, "id=" + playerLoaderObj.id + "&type=flash" + "&secure=" + playerLoaderObj.secure)
    }

    /**
     * Finds all actual video links and titles in received data
     * @param data to search
     * @return List contains all founded items
     */
    private List<WebResourceItem> fetchItems(data,seasonID){
        List<WebResourceItem> items = [];
        if(data in Map){
            def playlist = data.playlist
            if(playlist){
                items.addAll(fetchItems(playlist,seasonID))
            }else{
                String link = data.file;
                String title =  data.comment
                title = title.replaceAll("с","c");
                title = title.replaceAll("С","C");
                title=title.replaceAll("\\r\\n|\\r|\\n", " ");
                title=title.replaceAll("<br>", " - ");
                title = new String(title.getBytes(),"UTF-8");
                items << new WebResourceItem(title: title, additionalInfo: ['link' :link,'thumbnailUrl': "http://cdn.seasonvar.ru/oblojka/"+seasonID+".jpg" ])
            }
        }else if (data in ArrayList){
            data.each{
                items.addAll(fetchItems(it,seasonID))
            }
        }
        return items;
    }
    @Override
    protected ContentURLContainer extractUrl(WebResourceItem webResourceItem, PreferredQuality preferredQuality) {
        return new ContentURLContainer(fileType: MediaFileType.VIDEO,thumbnailUrl:webResourceItem.additionalInfo.thumbnailUrl ,contentUrl: webResourceItem.additionalInfo.link);
    }
    private  String decrypt(String input){
        def codecPairs = [codecsMatrix.codecA, codecsMatrix.codecB].transpose();
        for(def pair: codecPairs){
            String output = decrypt(input,(ArrayList<String>)pair[0],(ArrayList<String>)pair[1]);
            if(output.endsWith(".xml")|| output.endsWith("}") || output.contains("list.xml?time=")) return output;
        }
        return null;
    }
    private String decrypt(String input,ArrayList<String> codecA, ArrayList<String> codecB) {
        String output = input;
        for(int i = 0; i<codecA.size();i++){
            output = swap(codecB[i],codecA[i],output);
        }
        BASE64Decoder decoder = new BASE64Decoder();
        output = new String(decoder.decodeBuffer(output),"UTF8");
        return output;
    }
    private String swap(String first, String second, String input) {
        String output = input.replace(first, "___");
        output = output.replace(second, first);
        output = output.replace("___", second);
        return output;
    }

    protected String openURL(URL url){
        URLConnection conn  = url.openConnection();
        conn.setRequestProperty("Cookie","sva=lVe324PqsI24");
        return conn.getInputStream().text
    }
    protected String postToURL(URL url, String postParams){
        HttpURLConnection connection  = (HttpURLConnection) new URL(PLAYER_PHP_URL).openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Host", "seasonvar.ru");
        connection.setRequestProperty("Connection", "keep-alive");
        connection.setRequestProperty("Content-Length", Integer.toString(postParams.length()));
        connection.setRequestProperty("Origin", SERVER_URI);
        connection.setRequestProperty("X-Requested-With", "XMLHttpRequest");
        connection.setRequestProperty("User-Agent", USER_AGENT);
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        connection.setRequestProperty("Referer", url.toString());
        connection.setRequestProperty("Accept-Encoding", "gzip,deflate");
        connection.setRequestProperty("Accept", "text/html, */*; q=0.01");
        connection.setRequestProperty("Cookie","sva=lVe324PqsI24");
        connection.setDoOutput(true);
        connection.setDoInput(true);
        DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
        wr.writeBytes(postParams);
        wr.flush();
        wr.close();
        // Read page conten
        Reader decoder = new InputStreamReader("gzip".equals(connection.getContentEncoding())? new GZIPInputStream(connection.getInputStream()): connection.getInputStream());
        BufferedReader buffered = new BufferedReader(decoder);
        StringBuilder sb = new StringBuilder();
        String inputLine;
        while ((inputLine = buffered.readLine()) != null) {
            sb.append(inputLine);
        }
        buffered.close();
        return sb.toString();
    }
    @Override
    boolean extractorMatches(URL url) {
        return url ==~ VALID_WEB_RESOURCE_URL
    }

    @Override
    int getVersion() {
        return 19;
    }
    @Override
    String getExtractorName() {
        return getClass().getName()
    }
    static void main(args) {
        //def testUrl = new URL("http://seasonvar.ru/serial-12151-Geroi_Vozrozhdenie_serial_2015-1-season.html?translation=LostFilm");
        //def testUrl = new URL("http://seasonvar.ru/serial-13831-Killdzhojs-2-season.html");
        def testUrl = new URL("http://seasonvar.ru/serial-13730-Bezmozglye.html");
        //def testUrl = new URL("http://seasonvar.ru/serial-8881-Kuhnya-3-season.html");
        SeasonVar extractor = new SeasonVar() ;
        println "PluginName               : " + extractor.getExtractorName();
        println "TestMatch                : " + extractor.extractorMatches(testUrl);
        WebResourceContainer container = extractor.extractItems(testUrl, -1);
        container.getItems().each {
            println "URL                  : " + extractor.extractUrl(it, PreferredQuality.HIGH)
        }

    }
}
