import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.serviio.util.*

import groovy.json.JsonSlurper

import java.security.Key
import java.security.MessageDigest
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Cipher

/**
 * WebResource extractor plugin for M6 groupe replay
 *    M6
 *    http://www.m6replay.fr
 *       http://www.m6replay.fr/#/series-fictions
 *       http://www.m6replay.fr/#/jeunesse
 *       http://www.m6replay.fr/#/info
 *       http://www.m6replay.fr/#/jeunesse 
 *    W9
 *    http://www.m6replay.fr/#/w9
 *       http://www.m6replay.fr/#/w9/series-fictions
 *       http://www.m6replay.fr/#/w9/emissions
 *       http://www.m6replay.fr/#/w9/musique
 *    6ter
 *    http://www.m6replay.fr/#/6ter
 *       http://www.m6replay.fr/#/6ter/series-fictions
 *       http://www.m6replay.fr/#/6ter/emissions
 *       http://www.m6replay.fr/#/6ter/jeunesse
 *       http://www.m6replay.fr/#/6ter/documentaires
 *
 * @author Illico
 * @version 0.3
 *
 */
class M6group extends WebResourceUrlExtractor {

	final int VERSION = 3
	final VALID_FEED_URL = 'http://www.m6replay.fr.*'
   final CHANNEL_MATCH1 = '^http://www.m6replay.fr/#/(.*?)$'
   final CHANNELS = ['all', 'M6', 'W9', '6ter']
   final REPLAY_PROPERTY = "http://www.m6replay.fr/config/replay.property"
   final CATALOGUE_URL = "http://static.m6replay.fr/catalog/m6group_web/%s/catalogue.json"
   final CLIP_URL = "http://static.m6replay.fr/catalog/m6group_web/%s/clip/%s/clip_infos-%s.json"
   final CONF_URL = "http://static.m6replay.fr/mea/m6group_web/%s/getMeaConf.json"
   final RTMP_SERVER = 'rtmpe://groupemsix.fcod.llnwd.net'
   final RTMP_APP = 'a2883/e1'
   final IMAGES_URL = "http://static.m6replay.fr/images/"
   final SWF_URL = "http://www.m6replay.fr/rel-3/M6ReplayV3Application-3.swf" //"http://l3.player.m6.fr/swf/ReplayPlayerV2Hds.swf" //"http://www.m6replay.fr/rel-3/M6ReplayV3Application-3.swf" //"http://www.m6replay.fr/rel-1/M6ReplayV3Application-1.swf" // 
   final LOGO_M6GROUP = "http://www.groupem6.fr/img/logo_m6corpo.png"
   final LOGO_M6REPLAY = "http://www.groupem6.fr/img/carousel/m6replay.jpg"//"http://www.m6replay.fr/help/images/logo_m6.png"
   final LOGO_W9REPLAY = "http://www.groupem6.fr/img/carousel/w9replay.jpg"//"http://www.m6replay.fr/help/img_w9/logo_w9.png"
   final LOGO_6TERREPLAY = "http://www.groupem6.fr/img/carousel/6tereplay.jpg"
	
	String getExtractorName() {
		return "M6 group channels replay"
	}
   
	int getVersion() {
		return VERSION
	}
	
	boolean extractorMatches(URL feedUrl) {
		return feedUrl ==~ VALID_FEED_URL
	}

	WebResourceContainer extractItems(URL resourceUrl, int maxItems) {

      String channel
      String WebResourceTitle = CHANNELS[1]+" REPLAY"
		String WebResourceThumbnailUrl = LOGO_M6REPLAY
         //loginfo("resourceUrl: "+resourceUrl.toString());
      def channelmatcher = resourceUrl.toString() =~ CHANNEL_MATCH1
      if ( channelmatcher.getCount() == 0 ) {
         channel = code("M6");
      } else {
         switch (channelmatcher[0][1].toString().split('/')[0]) {
            case "m6" : channel = code("M6");break;
            case "w9" : channel = code("W9");break;
            case "6ter" : channel = code("6ter");break;
            default: channel = code("M6");break;
            }
      }
         loginfo("Channel : "+channel)        
		List<WebResourceItem> items = []   
      def full_catalog = get_json(String.format(CATALOGUE_URL, channel))
      full_catalog.clpList.each { key, value -> 
         if (value.type == "vi") {
            String WebResourceItemTitle			= value.programName+" - "+value.clpName+" - "+CSA(value.csa)
            String WebResourceItemChannel		   = channel
            String WebResourceItemClip    	   = key
            String WebResourceItemDate          = value.antennaDate
            String WebResourceItemThumbnailUrl	= IMAGES_URL+value.img.vignette
               //loginfo("Title: "+WebResourceItemTitle+", Clip: "+WebResourceItemClip+", thumb: "+WebResourceItemThumbnailUrl)
            WebResourceItem item = new WebResourceItem(title: WebResourceItemTitle, additionalInfo: ['WebResourceItemChannel': WebResourceItemChannel, 'WebResourceItemClip':WebResourceItemClip, 'WebResourceItemThumbnailUrl':WebResourceItemThumbnailUrl, 'WebResourceItemDate':WebResourceItemDate])
            items << item
           }
        }
      items = items.sort{ it.title } // Tri par titre
      //items = items.sort{ it.additionalInfo.WebResourceItemDate} // Tri par Date de diffusion
		return new WebResourceContainer(title: WebResourceTitle, thumbnailUrl: WebResourceThumbnailUrl, items: items)
	}

	ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
		String channel	= item.getAdditionalInfo()['WebResourceItemChannel']
		String clip = item.getAdditionalInfo()['WebResourceItemClip']
      String thumblUrl = item.getAdditionalInfo()['WebResourceItemThumbnailUrl']
         //loginfo("WebResourceItemChannel :"+channel+"\nWebResourceItemClip : "+clip+"\nWebResourceItemThumbnailUrl :"+thumblUrl);
      String MediaUrl = get_clip_url(channel,clip)[0]
         //loginfo("MediaUrl: "+MediaUrl);
		def cacheKey = clip + "_${requestedQuality}"
		return new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: MediaUrl, thumbnailUrl: thumblUrl, expiresImmediately: true, cacheKey : cacheKey)
	}

   private String code(String channel) {
    // Return the channel code
    return channel.toLowerCase() + 'replay'
    }
    
   private String CSA(String csacode) {
    // Return the CSA code
      //loginfo("CSA: "+csacode);    
         switch (csacode) {
            case "1" : return "!TP!";break;
            case "2" : return "!!-10!!";break;
            case "3" : return "!!!-12!!!";break;
            case "4" : return "!!!!-16!!!!";break;
            default: return "!TP!";break;
            } 
    }    

   private def get_json(String url){
    //Return the json-encode content of the HTTP GET request
      try {
         loginfo(String.format("JSON request: %s", url));
         String r = (new URL(url)).getText("utf-8");
         def json = new JsonSlurper().parseText(r);
         return json;
         } catch (Exception e) {
         loginfo(String.format("JSON request failed", url));
         return null;
         }
   }

   /*private def get_catalog(String channel) {
      // Return a catalog with only the needed information
      def full_catalog = get_json(String.format(CATALOGUE_URL, channel))
      // Only get main genres (no parent)
      def genres
      full_catalog.gnrList.each { key, value -> (value.idParent == null) ? loginfo("id: "+key+",label: "+value.name+",thumb: "+IMAGES_URL+value.img.vignette) : null; }
      //Get programs with visible clips
      def programs
      full_catalog.pgmList.each { key, value -> (value.clpList.vi != null) ? loginfo("id: "+key+",label: "+value.name+",thumb: "+IMAGES_URL+value.img.vignette+",clips :"+value.clpList.vi) : null; }
      // Get visible clips
      def clips
      full_catalog.clpList.each { key, value -> (value.type == "vi") ? loginfo("id: "+key+",label: "+value.programName+" - "+value.clpName+",thumb: "+IMAGES_URL+value.img.vignette+",id_pgm :"+value.idPgm) : null; }
      return ['genres': genres,
              'programs': programs,
              'clips': clips]
   }*/

   private def get_clip_url(channel, clip) {
      //Return the playable url of the clip
      String clip_key = clip[-2..-1]+'/'+clip[-4..-3]
         //loginfo("clip_key :"+clip_key)
      def asset = get_json(String.format(CLIP_URL,channel,clip_key,clip)).asset
      List urls = []    
      asset.each { key, value -> urls << value.url }
      urls.collect{
         // Look for a mp4 url
         if ( it.startsWith('mp4:')) { return get_rtmp_url(it) }
         // No mp4 url found, try to convert it from the f4m url
         if (it.endsWith('.f4m')) { 
            String link = 'mp4:production/regienum/'+it.split('/')[-1].replace('.f4m','.mp4');
            return get_rtmp_url(link)
         // No url found
         }
      }
   }

   private def encode_playpath(app, playpath, timestamp) {
      //Return the encoded token url (with hash)
      long delay = 86400
      String secret_key = 'vw8kuo85j2xMc'
      String url = String.format('%s?e=%d', playpath, timestamp + delay)//String url = String.format('%s?s=%d&e=%d', playpath, timestamp, timestamp + delay)
         //loginfo("url:"+url)
      String url_hash = MD5hash( secret_key+"/"+app+"/"+url.substring(4, url.length()))
         //loginfo("url_hash:"+url_hash)
      String token_url = url + '&h=' + url_hash
         //loginfo("token_url:"+token_url)
    return token_url
    }

   private String get_rtmp_url(playpath) {
      //Return the playable rtmp url
      String rtmp = RTMP_SERVER
      String app = RTMP_APP
		int timestamp = (getServerDate()).getTime() / 1000;      
      String token_url = encode_playpath(app, playpath, timestamp)
      String rtmp_url = rtmp+'/'+app+'/'+token_url+' timeout=10'
    return rtmp_url 
    }
    
   private String M6decrypt(byte[] data,byte[] key){
		try {
			Key clef = new SecretKeySpec(key,"DES");
			Cipher c = Cipher.getInstance("DES");
			c.init(Cipher.DECRYPT_MODE,clef);
			return new String(c.doFinal(data));
		} catch (Exception e) {
			System.out.println(e);
		return null;
		}
	}

   private String loginfo(String text) {
      log(text);
      println(text);
      }
      
	private Date getServerDate() {
		return new Date();
	}

	private String MD5hash(String toHash) {
		byte[] hash;
		try {
			hash = MessageDigest.getInstance("MD5").digest(toHash.getBytes());
		} catch (Exception e) {
			System.out.println(e);
		return null;
		}
		StringBuilder hashString = new StringBuilder();
		for (int i = 0; i < hash.length; i++) {
			String hex = Integer.toHexString(hash[i]);
			if (hex.length() == 1) {
				hashString.append('0');
				hashString.append(hex.charAt(hex.length() - 1));
			} else
				hashString.append(hex.substring(hex.length() - 2));
		}
		return hashString.toString();
	}
	
	static WebResourceContainer testURL(String url, int itemCount = 2) {
		M6group extractor = new M6group();
		URL resourceUrl = new URL(url);
		println "getExtractorName : " + extractor.getExtractorName();
		println "getVersion : " + extractor.getVersion();
			assert extractor.extractorMatches(resourceUrl), 'Url doesn\'t match for this WebResource plugin'
		println "extractorMatches : " + extractor.extractorMatches(resourceUrl);
		WebResourceContainer container = extractor.extractItems(resourceUrl, itemCount);
			assert container != null, 'Container is empty'
			assert container.items != null, 'Container contains no items'
			//assert container.items.size() == itemCount, 'Amount of items is invalid. Expected was ' + itemCount + ', result was ' + container.items.size()
		println "extractItems : " + container.items.size()
		println "***** HIGH *****";extractor.extractUrl(container.getItems()[1], PreferredQuality.HIGH);
		println "**** MEDIUM ****";extractor.extractUrl(container.getItems()[1], PreferredQuality.MEDIUM);
		println "***** LOW ******";extractor.extractUrl(container.getItems()[1], PreferredQuality.LOW);
		return container
	}	

	static void main(args) {
		testURL("http://www.m6replay.fr",-1)
      //testURL("http://www.m6replay.fr/#/m6",-1)
      //testURL("http://www.m6replay.fr/#/w9",-1)
      //testURL("http://www.m6replay.fr/#/w9/series-fictions",-1)
      //testURL("http://www.m6replay.fr/#/6ter",-1)
      //testURL("http://www.m6replay.fr/#/6ter/series-fictions",-1)
	}
}