package com.spklabs.ntv.admin;

import com.coolservlets.forum.Authorization;
import com.coolservlets.forum.Forum;
import com.coolservlets.forum.ForumFactory;
import com.coolservlets.forum.ForumPermissions;
import com.coolservlets.forum.util.admin.AdminUtils;
import com.coolservlets.forum.xml.XMLForum;
import com.coolservlets.forum.xml.XMLPropertyManager;
import com.coolservlets.forum.xml.XMLUser;
import com.spklabs.basic.database.castor.*;
import com.spklabs.basic.util.*;
import com.spklabs.ntv.database.BroadcastRule;
import com.spklabs.ntv.database.Program;
import com.spklabs.ntv.database.ProgramForum;
import com.spklabs.ntv.database.ProgramType;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDO;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.QueryResults;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.math.BigDecimal;
import java.util.*;

/**
 * Менеджер программ
 * @author Yury Rakitin
 * @author Alexey Efimov
 */
public class ProgramManager extends LogObject {
  /** Объект */
  private static ProgramManager instance;

  /** Синхронизатор */
  private static Object synchronizer = new Object();

  /**
   * Возвращает новый объект
   */
  public static ProgramManager getInstance() {
    // Проверяем есть ли уже такой объект
    if (instance == null) {
      synchronized (synchronizer) {
        if (instance == null) {
          // Создаем новый объект
          instance = new ProgramManager();
        }
      }
    }
    return instance;
  }

  /**
   * Создает новую запись и записывает в неё объект программы. В случае удачного создания
   * в поле id будет занесён идентификатор записи.
   * @param program - объект программы
   */
  public void createProgram(Program program) throws CastorJDOCreateException {
    CastorJDO.create(program);
  }

  /**
   * Обновляет имеющуюся запись программы в базе. Метод по идентификатору получает persisten
   * объект из базы, копирует в него новые данные и сохраняет результат в базе
   * @param program объект программы
   */

  public void updateProgram(Program program) throws CastorJDOUpdateException {
    CastorJDO.update(program);
  }

  /**
   * Возвращает объект программы
   * @param idProgram - идентификатор программы
   */
  public Program getProgram(String idProgram) throws CastorJDOLoadException {
    return (Program)CastorJDO.load(Program.class, idProgram);
  }

  /**
   * Возвращает объект программы
   * @param path - путь программы
   */
  public Program getProgramByPath(String path) throws CastorJDOLoadException {
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    Program program = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery(CastorJDO.prepareOQLSelect(Program.class, "WHERE path = $1"));
      query.bind(path);
      QueryResults results = query.execute();
      if (results.hasMore()) {
        program = (Program)results.next();
      }
      db.commit();
    } catch (Exception ex) {
      log.error(ex, ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
    return program;
  }

  /**
   * Возвращает список программ, упорядоченный по алфавиту
   * @param withDeleted если true, то будут включены программы с признаком о удалении
   * @return List программ
   */
  public List getProgramList(boolean withDeleted) {
    List programList = new ArrayList();
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery(CastorJDO.prepareOQLSelectSorted(Program.class, "name", SortDirection.ASC, "WHERE deleted = $1"));
      query.bind(withDeleted);
      programList = CastorJDO.list(query);
      db.commit();
    } catch (Exception ex) {
      log.error(ex, ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
    return programList;
  }

  /**
   * Возварщает список программ заданного типа, отсортированный в алфавитном порядке
   */
  public List getProgramList(ProgramType programType) {
    List programList = new ArrayList();
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery(CastorJDO.prepareOQLSelectSorted(Program.class, "name", SortDirection.ASC, "WHERE programTypeIntValue = $1 AND deleted = $2"));
      query.bind(programType.intValue());
      query.bind(false);
      programList = CastorJDO.list(query);
      db.commit();
    } catch (Exception ex) {
      log.error(ex, ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
    return programList;
  }
  /**
   * Возвращает список программ, упорядоченный по алфовиту
   * @return List программ
   */
  public List getProgramList() {
    return getProgramList(false);
  }

  /**
   * Возвращает списко "системных" программ, упорядоченный по алфовиту
   * @return список программ
   */
  public List getSystemProgramList() {
    List sysPrograms = new ArrayList();
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery(CastorJDO.prepareOQLSelectSorted(Program.class, "name", SortDirection.ASC, "WHERE system = $1"));
      query.bind(true);
      sysPrograms = CastorJDO.list(query);
      db.commit();
    } catch (Exception ex) {
      log.error(ex, ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
    return sysPrograms;
  }


  /**
   * Возвращает список программ
   * @return List программ
   */
  public String getProgramListXML() {
    List list = CastorJDO.list(Program.class);
    return XMLUtils.documentToString(CastorJDO.listToDocument(list));
  }

  /**
   * Возвращает все программы
   * @return Collection программ
   * @deprecated Используйте {@ link #getProgramList getProgramList}
   */
  public Collection getPrograms() {
    return getProgramList();
  }

  /**
   * Устанавливает программам признак того, что они удалены
   * @param ids список идентификаторов программы
   */
  public void setDeletedProgramList(List ids) throws CastorJDOUpdateException {
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      String query = CastorJDO.prepareOQLSelectInList(Program.class, ids);
      List programList = CastorJDO.list(query);
      db.commit();

      Iterator it = programList.iterator();
      while (it.hasNext()) {
        Program program = (Program)it.next();
        program.setDeleted(true);
      }

    } catch (Exception ex) {
      throw new CastorJDOUpdateException(ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
  }

  /**
   * Получает программу и устанавливает её признак, того что она удалена. Также будут удалены
   * все правила выхожа в эфир этой программы
   * @param idProgram иденитфикатор программы
   */
  public void setDeletedProgram(String idProgram) throws CastorJDORemoveException {
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery(CastorJDO.prepareOQLSelect(BroadcastRule.class, "WHERE idProgram=$1"));
      query.bind(idProgram);
      QueryResults results = query.execute();
      while (results.hasMore()) {
        db.remove(results.next());
      }
      // получаем и выставляем её признак, считать её удаленной
      query = db.getOQLQuery(CastorJDO.prepareOQLSelect(Program.class, "WHERE id=$1"));
      query.bind(idProgram);
      results = query.execute();
      if (results.hasMore()) {
        Program program = (Program)results.next();
        program.setDeleted(true);
      }
      db.commit();
    } catch (Exception ex) {
      throw new CastorJDORemoveException(ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
  }


  /**
   * Удалаяет объект программы из базы. Также будут удалены правила выхода в эфир (BroadcasrRule) и ссылки
   * этой программы на форумы (ProgramForum)
   * @param idProgram идентификатор программы
   */
  public void removeProgram(String idProgram) throws CastorJDORemoveException {
    JDO jdo = CastorJDO.jdo();
    Database db = null;
    try {
      db = jdo.getDatabase();
      db.begin();
      // получаем и удаляем все правила выхода в эфир этой программы
      OQLQuery query = db.getOQLQuery("SELECT br FROM " + BroadcastRule.class.getName() + " br WHERE idProgram=$1");
      query.bind(idProgram);
      QueryResults results = query.execute();
      while (results.hasMore()) {
        db.remove(results.next());
      }
      // получаем и удаляем все ссылки на форумы
      query = db.getOQLQuery("SELECT pf FROM " + ProgramForum.class.getName() + " pf WHERE idProgram=$1");
      query.bind(idProgram);
      results = query.execute();
      while (results.hasMore()) {
        db.remove(results.next());
      }
      Program persistentProgram = (Program)db.load(Program.class, idProgram);
      db.remove(persistentProgram);
      db.commit();
    } catch (Exception ex) {
      throw new CastorJDORemoveException(ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }
  }

  /**
   * Создает форум для программы. Если форум уже есть возвращается первый
   * из списка форумов, новый форум не создается.
   * @param idProgram идентификатор программы
   * @return Forum Форум программы
   */
  public Forum createForum(String idProgram) {
    Authorization authorization = AdminUtils.getAdminAuthorization();
    Forum forum = null;
    try {
      List forums = getForumListByProgram(idProgram, authorization);
      if (forums.size() > 0) {
        // Форумы уже есть, возвращаем первый
        forum = (Forum)forums.get(0);
      } else {
        // Создаем форум
        Program program = getProgram(idProgram);
        String name = program.getName();
        if (StringUtils.isEmpty(name)) {
          name = "Программа #" + program.getId();
        }
        name = "НТВ-Форум: " + name;
        // Создаем форум
        try {
          ForumFactory factory = ForumFactory.getInstance(authorization);
          forum = factory.createForum(name, "");
          forum.getPermissionsManager().addAnonymousUserPermission(ForumPermissions.READ);
          forum.getPermissionsManager().addRegisteredUsersPermission(ForumPermissions.READ);
          forum.getPermissionsManager().addRegisteredUsersPermission(ForumPermissions.CREATE_THREAD);
          forum.getPermissionsManager().addRegisteredUsersPermission(ForumPermissions.CREATE_MESSAGE);
          ProgramForum programForum = new ProgramForum();
          programForum.setIdForum(String.valueOf(forum.getID()));
          programForum.setIdProgram(idProgram);
          CastorJDO.create(programForum);
        } catch (Exception ex) {
          log.error(ex, ex);
        }
      }
    } catch (Exception ex) {
      log.error(ex, ex);
    }
    return forum;
  }

  /**
   * Возвращает форум программы
   * @param idProgram Идентификакор программы
   * @param authorization Авторизация в форумах
   */
  public Forum getForumByProgram(String idProgram, Authorization authorization) {
    List forums = new ArrayList();
    try {
      forums = getForumListByProgram(idProgram, authorization);
    } catch (Exception ex) {
      log.error(ex, ex);
    }
    if (forums.isEmpty()) {
      // Создаем форум
      return createForum(idProgram);
    } else {
      return (Forum)forums.get(0);
    }
  }

  /**
   * Возвращает форум программы
   * @param path Путь программы
   * @param authorization Авторизация в форумах
   */
  public Forum getForumByProgramPath(String path, Authorization authorization) {
    try {
      Program program = getProgramByPath(path);
      return getForumByProgram(program.getId(), authorization);
    } catch (Exception ex) {
      log.error(ex, ex);
    }
    return null;
  }

  /**
   * Возвращает список форумов
   * @param idProgram Идентификаор программы
   * @param authorization Авторизация в форумах
   */
  public List getForumListByProgram(String idProgram, Authorization authorization) throws CastorJDOLoadException {
    List forumIds = new ArrayList();
    JDO jdo = null;
    Database db = null;
    jdo = CastorJDO.jdo();
    db = null;
    try {
      db = jdo.getDatabase();
      db.begin();

      String oql = CastorJDO.prepareOQLSelectField(ProgramForum.class, "idForum", "WHERE idProgram = $1");
      OQLQuery query = db.getOQLQuery(oql);
      query.bind(idProgram);

      forumIds = CastorJDO.list(query);

      db.commit();
    } catch (Exception ex) {
      throw new CastorJDOLoadException(ex);
    } finally {
      CastorJDO.closeDatabase(db);
    }

    // Получаем список форумов
    final ForumFactory factory = ForumFactory.getInstance(authorization);
    List forums = new ArrayList();
    forums.addAll(
      CollectionUtils.slide(forumIds,
        new CollectionSlider() {
          /**
           * Преобразование из id в Forum
           * @param o Идентификатор
           * @return Forum Object
           * @exception java.util.NoSuchElementException если форум не найден или id не правильный, в любом
           * случае выброс этого exception гарантирует, что элемент пропускается и не включается
           * в выходной список
           */
          public Object slide(Object o) throws NoSuchElementException {
            try {
              long id = o instanceof BigDecimal ? ((BigDecimal)o).longValue() : Long.parseLong(o.toString());
              return factory.getForum(id);
            } catch (Exception ex) {
              throw new NoSuchElementException();
            }
          }
        }
      )
    );
    return forums;
  }

  /**
   * Возвращает список форумов программы ввиде XML
   * @param idProgram идентификтор программы
   * @param authorization авторизация для доступа к форумам
   * @return список форумов в XML
   */
  public String getForumListByProgramXML(String idProgram, Authorization authorization) {
    Document document = XMLUtils.createDocument();
    Element xmlForumList = document.createElement("ForumList");
    document.appendChild(xmlForumList);
    try {
      List forumList = getForumListByProgram(idProgram, authorization);
      if (!forumList.isEmpty()) {
        try {
          XMLPropertyManager pm = new XMLPropertyManager();
          pm.set(XMLForum.class, true);				      // Forum
          pm.set(XMLForum.ThreadList.class, false);	// ThreadList
          pm.set(XMLUser.class, false);				      // User


          Iterator it = forumList.iterator();
          while (it.hasNext()) {
            Forum forum = (Forum)it.next();
            XMLUtils.appendDocument(xmlForumList, new XMLForum(forum).getDocument(pm));
          }
        } catch (Exception ex) {
          log.error(ex, ex);
        }
      }
    } catch (Exception ex) {
      log.error(ex, ex);
    }
    return XMLUtils.documentToString(document);
  }
}
