package org.bucksch.zeipis.importer;
import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.Date;
import org.bucksch.zeipis.all.Misconfiguration;

import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class XMLTVImporter
{
  protected PreparedStatement findPerson;
  protected PreparedStatement addPerson;
  protected PreparedStatement addCredit;
  protected PreparedStatement addFilm;
  protected PreparedStatement findFilm;
  protected PreparedStatement findFilmByTitle;
  protected PreparedStatement addBroadcast;
  protected PreparedStatement findStation;
  protected void createSQLStatements()
    throws SQLException
  {
    findPerson = DB.prepareStatement("SELECT personid FROM person WHERE realname = ?");
    addPerson = DB.prepareStatement("INSERT INTO person (realname) VALUES (?)", Statement.RETURN_GENERATED_KEYS);    
    addCredit = DB.prepareStatement("INSERT INTO credits (filmid, personid, role, function) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);    
    findFilm = DB.prepareStatement("SELECT filmid FROM film WHERE sourceid = ?");
    findFilmByTitle = DB.prepareStatement("SELECT filmid FROM film WHERE title = ? AND subtitle = ?");
    addFilm = DB.prepareStatement("INSERT INTO film (title, subtitle, original_title, original_subtitle, description, showtype, genre, seriesid, episodeid, firstshown, quality_rating, sourceid, xmltv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);    
    addBroadcast = DB.prepareStatement("INSERT INTO broadcast (filmid, stationid, starttime, endtime) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE filmid = ?");    
    findStation = DB.prepareStatement("SELECT stationid FROM station WHERE xmltvid = ?");
  }
	public XMLTVImporter(Connection db, InputStream xmltvfile)
    throws Exception
	{
    DB = db;
    createSQLStatements();

    System.out.print("parsing...");
		Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                   .parse(xmltvfile);
    System.out.println("done");
    Element tv = (Element) doc.getElementsByTagName("tv").item(0);
    NodeList programmes = tv.getElementsByTagName("programme");
    for (int i = 0; i < programmes.getLength(); i++)
      parseProgramme((Element) programmes.item(i));
	}
  public void parseProgramme(Element prog)
  {
    try {
      int stationid = getStation(prog.getAttribute("channel"));
      Date start = xmltvdateparser.parse(prog.getAttribute("start"));
      Date end = xmltvdateparser.parse(prog.getAttribute("stop"));

      int filmid = parseFilm(prog);

      //filmid = ?, stationid = ?, starttime = ?, endtime = ?, filmid = ?
      addBroadcast.setInt(1, filmid);
      addBroadcast.setInt(5, filmid);
      addBroadcast.setInt(2, stationid);
      addBroadcast.setTimestamp(3, new java.sql.Timestamp(start.getTime()));
      addBroadcast.setTimestamp(4, new java.sql.Timestamp(end.getTime()));
      addBroadcast.executeUpdate();
    } catch (SQLException e) {
      e.printStackTrace(System.err);
    } catch (Misconfiguration e) {
      //warning(e.getMessage()); - XXX tons of "station not found"
    } catch (Exception e) {
      //System.err.println(e + " when parsing " + prog.toString());
      e.printStackTrace(System.err);
    }
  }
  public int parseFilm(Element prog)
    throws Exception
  {
    Node n;
    
    String title = null;
    String origtitle = null;
    String subtitle = "";
    try {
      NodeList titles = prog.getElementsByTagName("title");
      for (int i = 0; i < titles.getLength(); i++)
      {
        Element titleE = (Element) titles.item(i);
        if ("yes".equals(titleE.getAttribute("original")))
          origtitle = titleE.getTextContent();
        else
          title = titleE.getTextContent();
      }
    } catch (Exception e) { errorInOptional(e); }
    try {
      if ((n = prog.getElementsByTagName("sub-title").item(0)) != null)
        subtitle = n.getTextContent();
    } catch (Exception e) { errorInOptional(e); }

    String sourceid = null;
    try {
      if ((n = prog.getElementsByTagName("id").item(0)) != null ||
          (n = prog.getElementsByTagName("episode-num").item(0)) != null) // TODO HACK used my xmltv
      {
        String idscheme = ((Element) n).getAttribute("system");
        if (idscheme != "onscreen")
          sourceid = n.getTextContent() + "-" + idscheme;
      }
      if (sourceid == null)
        sourceid = String.valueOf(Math.abs(title.hashCode())) +
            (subtitle.length() == 0 ? "" : "-" + String.valueOf(Math.abs(subtitle.hashCode()))) + 
            "-zeipisgenerated";
    } catch (Exception e) { errorInOptional(e); }

    // Search whether existing film with that ID exists
    findFilm.setString(1, sourceid);
    ResultSet rs = findFilm.executeQuery();
    if (rs.first())
      return rs.getInt(1);

    // Search whether existing film with that title exists
    if (matchFilmByTitle)
    {
      findFilmByTitle.setString(1, title);
      findFilmByTitle.setString(2, subtitle);
      rs = findFilmByTitle.executeQuery();
      if (rs.first())
        return rs.getInt(1);
    }

    // parse rest and add it
    String desc = null;
    String genre = null;
    String showtype = null;
    int firstshown = 0;
    float quality_rating = -1;
    String xmltv = null;
    try {
      if ((n = prog.getElementsByTagName("desc").item(0)) != null)
        desc = n.getTextContent();
    } catch (Exception e) { errorInOptional(e); }
    try {
      if ((n = prog.getElementsByTagName("category").item(0)) != null)
        genre = n.getTextContent();
    } catch (Exception e) { errorInOptional(e); }
    try {
      if ((n = prog.getElementsByTagName("showtype").item(0)) != null)
        showtype = n.getTextContent();
    } catch (Exception e) { errorInOptional(e); }
    try {
      n = prog.getElementsByTagName("date").item(0);
      if (n != null)
      {
        String firstshownS = n.getTextContent();
        if (firstshownS.length() == 4) // only year
          firstshown = Integer.valueOf(firstshownS);
        else
          //firstshown = DateFormat.getDateInstance().parse(firstshownS).getYear();
          throw new Exception("Not implemented");
      }
    } catch (Exception e) { errorInOptional(e); }    
    try {
      n = prog.getElementsByTagName("star-rating").item(0);
      if (n != null)
      {
        String r = n.getTextContent();
        String[] v = r.split("/", 2);
        quality_rating = Float.valueOf(v[0]) / Float.valueOf(v[1]);
      }
    } catch (Exception e) { errorInOptional(e); }    
    try {
      StringWriter w = new StringWriter();
      new XMLSerializer(w, null).serialize(prog);
      xmltv = w.toString();
    } catch (Exception e) { errorInOptional(e); }

    //title = ?, subtitle = ?, original_title = ?, original_subtitle = ?, description = ?,
    //showtype = ?, genre = ?, seriesid = ?, episodeid = ?, firstshown = ?,
    //quality_rating = ?, sourceid = ?, xmltv = ?
    addFilm.setString(1, title);
    addFilm.setString(2, subtitle);
    addFilm.setString(3, origtitle);
    addFilm.setString(4, null);
    addFilm.setString(5, desc);
    addFilm.setString(6, showtype);
    addFilm.setString(7, genre);
    addFilm.setString(8, null);
    addFilm.setString(9, null);
    if (firstshown != 0)
      addFilm.setInt(10, firstshown);
    else
      addFilm.setNull(10, java.sql.Types.INTEGER);
    addFilm.setFloat(11, quality_rating);
    addFilm.setString(12, sourceid);
    addFilm.setString(13, xmltv);
    //System.out.println("Adding " + title + " " + subtitle);

    addFilm.executeUpdate();
    rs = addFilm.getGeneratedKeys();
    if (!rs.first())
      throw new Exception("");
    int filmid = rs.getInt(1); 

    try {
      if ((n = prog.getElementsByTagName("credits").item(0)) != null)
      {
        NodeList credits = n.getChildNodes();
        for (int i = 0; i < credits.getLength(); i++)
          if (credits.item(i) instanceof Element)
            parseCredit((Element) credits.item(i), filmid);
      }
    } catch (Exception e) { errorInOptional(e); }

    return filmid;
  }
  public void parseCredit(Element credit, int filmid)
  {
    try {
      String realname = credit.getTextContent();
      String rolename = "";
      if (credit.getNodeName().equals("actor"))
        rolename = credit.getAttribute("role");
      int personid = 0;
      findPerson.setString(1, realname);
      ResultSet rs = findPerson.executeQuery();
      if (rs.first())
        personid = rs.getInt(1);
      else
      {
        addPerson.setString(1, realname);
        addPerson.executeUpdate();
        rs = addPerson.getGeneratedKeys();
        if (!rs.first())
          throw new Exception("New person ID was not returned");
        personid = rs.getInt(1);
      }
      addCredit.setInt(1, filmid);
      addCredit.setInt(2, personid);
      addCredit.setString(3, rolename);
      addCredit.setString(4, credit.getNodeName());
      addCredit.executeUpdate();
    } catch (Exception e) { errorInOptional(e); }
  }
  protected int getStation(String xmltvid)
    throws Exception
  {
    if (stationCache.containsKey(xmltvid))
      return stationCache.get(xmltvid);
    findStation.setString(1, xmltvid);
    ResultSet rs = findStation.executeQuery();
    if (!rs.first())
      throw new Misconfiguration("Station " + xmltvid + " not found in DB");
    int id = rs.getInt("stationid");
    stationCache.put(xmltvid, id);
    return id;
  }
  protected void errorInOptional(Exception e)
  {
    //System.err.println(e);
    warning(e.getMessage());
    e.printStackTrace(System.err);
  }
  protected void warning(String msg)
  {
    System.err.println(msg);
  }
  protected Connection DB;
  protected Map<String, Integer> stationCache = new HashMap<String, Integer>(); // XMLTV ID -> DB ID
  protected static DateFormat yearparser = new SimpleDateFormat("yyyy");
  protected static DateFormat xmltvdateparser = new SimpleDateFormat("yyyyMMddHHmmss");
  
  // Prefs
  protected static boolean matchFilmByTitle = true;
}
