package com.upc.pbe.upcnews;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.net.URL;

import android.content.Context;
import android.widget.Toast;

//Parser d'arxius m3u8
public class Parser
{

	private static final String STARTWORD = "#EXTM3U";
	private String path; // CWD, completes implicit paths such as "file.m3u8"
							// appending the CWD in order to download the file
	private int fileType; // Indica si es segment list o media list
	private int currentLine;
	private int currentSegment; // Cada segment de cada qualitat distinta
	private int currentList; // Index de cada recurso distint
	private boolean expectSegment = false;
	private boolean expectList = false;
	private Context ctx; // On enviar les notificacions i warnings
	private boolean validated; // Indica si la llista comen�a amb #EXTM3U o no
	private String server;

	public Parser(String p, Context caller)
	{
		ctx = caller;
		path = p;
		String host = p.substring(7);
		server = host.substring(0, host.indexOf("/"));
		currentLine = 0;
		currentSegment = 0;
		currentList = 0;
		fileType = -1;// -1 indica UNDETERMINED, 0 indica SEGMENTS, 1 indica
						// MEDIA
		validated = false;
	}

	public ArrayList<ParentList> parseFile(String file) throws ErrorException, WarningException, InfoException
	{
		// Parseja un arxiu m3u8
		ArrayList<ParentList> lists = new ArrayList<ParentList>();
		lists.add(new ParentList(""));
		lists.get(0).getLists().add(new Video(-1));
		String[] lines = file.split("\n");
		for (int i = 0; i < lines.length; i++)
		{
			if (lines[i].endsWith("\\"))
			{
				// Barreja les entrades multilinea, que son les que acaben en
				// backslash
				lines[++i] = lines[i - 1].substring(0, lines[i - 1].indexOf("\\") - 1)
						+ lines[i];
			}
			try
			{
				parseLine(lines[i], lists);
			}
			catch (InfoException iE)
			{
				Toast.makeText(ctx, iE.getMessage(), Toast.LENGTH_SHORT).show();
			}
			catch (WarningException wE)
			{
				Toast.makeText(ctx, wE.getMessage(), Toast.LENGTH_SHORT).show();
			}
		}
		/*
		 * Esto contiene una lista de RECURSOS, que a su vez contiene una lista
		 * de CALIDADES. Si no te gusta mis VERSOS, te mando a la mierda sin
		 * SUTILIDADES.
		 * 
		 * -- Imanol, hasta las cejas de cafeina.
		 * 
		 * PD: En el ultimo nivel de profundidad hay una lista de segmentos
		 * (debajo de calidades). Alli esta el autentico tesoro de Narnia.
		 */
		for (int i = 0; i < lists.size(); i++)
		{
			sortQuality(lists.get(i));
		}
		return lists;
	}

	private int searchID(String ID, ArrayList<ParentList> lists)
	{
		for (int i = 0; i < lists.size(); i++)
		{
			if (lists.get(i).getID().equals(ID))
			{
				return i;
			}
		}
		return -1;
	}

	private void sortQuality(ParentList ppls)
	{
		ArrayList<Video> lists = ppls.getLists();
		if (lists.get(0).getQuality() == -1)
		{
			// Es el cas de Media Lists i Segment Lists, no tenen diferenciacio
			// de qualitats
			return;
		}
		// Bubblesort
		while (true)
		{
			boolean sorted = true;
			int i = 0;
			do
			{
				if (lists.get(i).getQuality() < lists.get(i + 1).getQuality())
				{
					sorted = false;
					Video aux = lists.get(i);
					lists.set(i, lists.get(i + 1));
					lists.set(i + 1, aux);
				}

			}
			while (++i != lists.size() - 1);
			if (sorted)
			{
				break;
			}
		}

	}

	public void parseLine(String line, ArrayList<ParentList> lists) throws ErrorException, WarningException, InfoException
	{
		// Parseja una linea de l'arxiu
		if (line.isEmpty())
		{
			currentLine++;
			return;
		}
		else
		{
			ParentList ppls = lists.get(currentList);
			Video pls = ppls.getLists().get(ppls.getLists().size() - 1);
			if (!validated)
			{
				if (line.equals(STARTWORD))
				{
					validated = true;
				}
				else
				{
					throw new ErrorException("Playlist no valida");
				}
			}
			else
			{
				if (line.charAt(0) == '#')
				{
					if (line.substring(0, 4).equals("#EXT"))
					{
						if (expectSegment
								&& !line.startsWith("#EXT-X-BYTERANGE"))
						{
							throw new ErrorException("S'esperava segment URI, en comptes tenim EXT-TAG");
						}
						if (expectList)
						{
							throw new ErrorException("S'esperava llista, en comptes tenim EXT-TAG");
						}
						String[] extTag = line.split(":", 2);
						if (extTag[0].equals("#EXT-X-MEDIA-SEQUENCE"))
						{
							pls.setSequence(Integer.parseInt(extTag[1]));
						}
						else if (extTag[0].equals("#EXT-X-TARGETDURATION"))
						{
							pls.setMaxDuration(Integer.parseInt(extTag[1]));
						}
						else if (extTag[0].equals("#EXTINF"))
						{
							if (fileType == -1)
							{
								fileType = 0;
							}
							else if (fileType == 1)
							{
								throw new ErrorException("Arxiu invalid, hauria de contenir llistes pero s'han trobat segments");
							}
							String[] args = extTag[1].split(",");
							float duration = Float.parseFloat(args[0]);
							if (duration > pls.getMaxDuration())
							{
								throw new ErrorException("Segment "
										+ currentSegment + 1 + " en linea "
										+ currentLine
										+ " supera la duracio maxima");
							}
							Segment s = new Segment(duration);
							if (args.length == 1)
							{
								s.setName("");
							}
							else
							{
								s.setName(args[1]);
							}
							pls.getSegments().add(s);
							expectSegment = true;
						}
						else if (extTag[0].equals("#EXT-X-STREAM-INF"))
						{
							if (fileType == -1)
							{
								fileType = 1;
							}
							else if (fileType == 0)
							{
								throw new ErrorException("Arxiu invalid, hauria de contenir segments pero s'han trobat llistes");
							}
							expectList = true;
							String programID = "";
							double bandwidth = -1;
							String[] arguments = extTag[1].split(",");
							for (int i = 0; i < arguments.length; i++)
							{
								String[] argument = arguments[i].split("=");
								// Si l'argument en questio conte diferents
								// valors, detecta el caracter " i els ajunta en
								// un String
								if (argument[1].startsWith("\"")
										&& !argument[1].endsWith("\""))
								{
									int j = i;
									while (true)
									{
										if (i == arguments.length - 1)
										{
											throw new ErrorException("Argument mal expressat en linea "
													+ currentLine);
										}
										if (arguments[++i].endsWith("\""))
										{
											String end = arguments[i];
											for (int k = j + 1; k < i; k++)
											{
												argument[1] += arguments[k];
											}
											argument[1] += end;
											break;
										}
									}
								}
								if (argument[0].equals("PROGRAM-ID"))
								{
									programID = argument[1];
								}
								else if (argument[0].equals("BANDWIDTH"))
								{
									bandwidth = Double.parseDouble(argument[1]);
								}
							}
							if (programID.equals("") || bandwidth == -1)
							{
								throw new ErrorException("Playlist en linea "
										+ currentLine + " no te arguments");
							}
							if (!programID.equals(ppls.getID()))
							{
								// Si el Program-ID no coincideix, buscar o
								// crear un de nou
								if (ppls.getID().equals(""))
								{
									// Si es treballa amb la ParentList per
									// defecte, modificar, no crear una nova
									lists.get(0).setID(programID);
								}
								else
								{
									currentList = searchID(programID, lists);
									if (currentList == -1)
									{
										// No s'ha trobat ParentList, es crea
										// una nova
										lists.add(new ParentList(programID));
										currentList = lists.size() - 1;
									}
									ppls = lists.get(currentList);
									pls = ppls.getLists().get(ppls.getLists().size() - 1);
								}
							}
							// Creem una List dins el ParentList correcte
							if ((ppls.getLists().size() == 1)
									&& (pls.getQuality() == -1)
									&& pls.getSegments().isEmpty())
							{
								// Si es treballa amb la List per defecte,
								// modificar, no crear una nova
								pls.setQuality(bandwidth);
							}
							else
							{
								ppls.getLists().add(new Video(bandwidth));
							}
						}
						else if (extTag[0].equals("#EXT-X-MEDIA"))
						{
							if (fileType == -1)
							{
								fileType = 1;
							}
							else if (fileType == 0)
							{
								throw new ErrorException("Arxiu invalid, hauria de contenir segments pero s'han trobat llistes");
							}
							String Type = "";
							String Name = "";
							String GroupID = "";
							String URI = "";
							boolean Default = false;
							String[] arguments = extTag[1].split(",");
							for (int i = 0; i < arguments.length; i++)
							{
								String[] argument = arguments[i].split("=");
								// Si l'argument en questio conte diferents
								// valors, detecta el caracter " i els ajunta en
								// un String
								if (argument[1].startsWith("\"")
										&& !argument[1].endsWith("\""))
								{
									int j = i;
									while (true)
									{
										if (i == arguments.length - 1)
										{
											throw new ErrorException("Argument mal expressat en linea "
													+ currentLine);
										}
										if (arguments[++i].endsWith("\""))
										{
											String end = arguments[i];
											for (int k = j + 1; k < i; k++)
											{
												argument[1] += arguments[k];
											}
											argument[1] += end;
											break;
										}
									}
								}
								if (argument[0].equals("NAME"))
								{
									Name = argument[1].substring(1, argument[1].length() - 1);
								}
								else if (argument[0].equals("TYPE"))
								{
									Type = argument[1];
								}
								else if (argument[0].equals("GROUP-ID"))
								{
									GroupID = argument[1].substring(1, argument[1].length() - 1);
								}
								else if (argument[0].equals("DEFAULT"))
								{
									if (argument[1].equals("YES"))
									{
										Default = true;
									}
									else if (argument[1].equals("NO"))
									{
										Default = true;
									}
									else
									{
										throw new ErrorException("Valor invalid per l'argument DEFAULT en linea "
												+ currentLine);
									}
								}
								else if (argument[0].equals("URI"))
								{
									if (!argument[1].contains("http://"))
									{
										// En cas de que la ruta llegida es
										// RELATIVE
										URI = path
												+ argument[1].substring(1, argument[1].length() - 1);
									}
									else
									{
										URI = argument[1].substring(1, argument[1].length() - 1);
									}
								}
							}
							if (Type.equals("") || Name.equals("")
									|| URI.equals("") || GroupID.equals(""))
							{
								throw new ErrorException("Playlist en linea "
										+ currentLine + " no te arguments");
							}
							Parser p = new Parser(URI.substring(0, URI.lastIndexOf("/") + 1), ctx);
							Video newList;
							try
							{
								Descarrega download = new Descarrega(ctx);
								download.execute(new URL(URI));
								newList = p.parseFile(download.get()).get(0).getLists().get(0);
								if (!GroupID.equals(ppls.getID()))
								{
									if (ppls.getID().equals(""))
									{
										// Si es treballa amb la ParentList per
										// defecte, modificar, no crear una nova
										ppls.setDefault(Default);
										ppls.setName(Name);
										ppls.setType(Type);
										ppls.getLists().set(0, newList);
										ppls.setID(GroupID);
									}
									else
									{
										currentList = searchID(GroupID, lists);
										if (currentList == -1)
										{
											ParentList pl = new ParentList(GroupID);
											pl.setDefault(Default);
											pl.setName(Name);
											pl.setType(Type);
											lists.add(pl);
											currentList = lists.size() - 1;
										}
										ppls = lists.get(currentList);
										ppls.getLists().add(newList);
									}
								}
								else
								{
									ppls.getLists().add(newList);
								}
							}
							catch (IOException e)
							{
								// Nunca llegara aqui la cosa, y si lo hace ES
								// CULPA DEL SERVIDOR!!
							}
							catch(InterruptedException e)
							{
								
							}
							catch(ExecutionException e)
							{
								
							}
						}
						else if (extTag[0].equals("#EXT-X-ENDLIST"))
						{
							if (expectSegment || expectList)
							{
								throw new ErrorException("Final de llista no esperat!");
							}
						}
						else if (extTag[0].equals("#EXT-X-VERSION"))
						{
							// IGNORE
						}
						else if (extTag[0].equals("#EXT-X-PLAYLIST-TYPE"))
						{
							// IGNORE
						}
						else
						{
							currentLine++;
							throw new WarningException("Tag no implementat: "
									+ extTag[0]);
						}
					}
					else
					{
						String comment = line.substring(1);
						throw new InfoException("Comentari en linea "
								+ currentLine++ + " " + comment);
					}
				}
				else
				{
					if (expectSegment)
					{
						expectSegment = false;
						try
						{
							if (!line.startsWith("http://")
									&& !line.startsWith("/"))
							{
								line = path + line;
							}
							else if (line.startsWith("/"))
							{
								line = "http://" + server + line;
							}
							pls.getSegments().get(currentSegment).setURL(line);
						}
						catch (MalformedURLException e)
						{
							throw new ErrorException(e.getMessage());
						}
						currentSegment++;
					}
					else if (expectList)
					{
						// Descarregar llista i juntar les entrades amb les de
						// la llista actual
						expectList = false;
						Parser p = new Parser(line.substring(0, line.lastIndexOf("/") + 1), ctx);
						Video newList;
						try
						{
							Descarrega download = new Descarrega(ctx);
							download.execute(new URL(line));
							newList = p.parseFile(download.get()).get(0).getLists().get(0);
							pls.setMaxDuration(newList.getMaxDuration());
							pls.setSequence(newList.getSequence());
							for (int i = 0; i < newList.getSegments().size(); i++)
							{
								pls.getSegments().add(newList.getSegments().get(i));
							}
						}
						catch (IOException e)
						{
							// Nunca llegara aqui la cosa, y si lo hace ES CULPA
							// DEL SERVIDOR!!
						}
						catch(InterruptedException e)
						{
							
						}
						catch(ExecutionException e)
						{
							
						}
					}
					else
					{
						throw new ErrorException("ERROR: String no esperat"
								+ line);
					}
				}
			}
			currentLine++;
		}
	}
}