/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2011 Aimluck,Inc.
 * http://www.aipo.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package aipo.batch.userinfo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.resources.JetspeedResources;

import aipo.batch.utils.ALBatchGroupUtils;
import aipo.batch.utils.AipoPostComparatorById;
import aipo.batch.utils.AipoPostComparatorByLevel;
import aipo.batch.utils.BatchUtils;

import com.aimluck.eip.cayenne.om.account.AvzMPostPosition;
import com.aimluck.eip.cayenne.om.account.EipMPost;
import com.aimluck.eip.cayenne.om.security.TurbineGroup;
import com.aimluck.eip.common.ALEipManager;
import com.aimluck.eip.common.ALEipUser;
import com.aimluck.eip.orm.Database;
import com.aimluck.eip.orm.query.SelectQuery;
import com.aimluck.eip.util.ALEipUtils;

/**
 * <HR>
 * <p>
 * 
 * 組織情報のファイルを読み込み、Aipoのデータベースにインポートします。<br>
 * <P>
 * <HR>
 * <P>
 */
public class PostImporter {
  /** ロガー */
  private static final JetspeedLogger logger =
    JetspeedLogFactoryService.getLogger(PostImporter.class.getName());

  /** プロパティファイル */
  private static final String PROPERTY_FILE =
    JetspeedResources.getString("aipo.conf", "")
      + File.separator
      + "Batch.properties";

  /** 部署ファイル名 */
  private static final String POST_FILE_NAME =
    (String) BatchUtils.getProperties(PROPERTY_FILE).get(
      "import.post.file.name");

  /** 第4階層登録文字列：分室 */
  private static final String POST_4TH_LEVEL_REGIST_STRING =
    (String) BatchUtils.getProperties(PROPERTY_FILE).get(
      "post.4th.level.regist.string");

  /** グループ公開フラグ：公開 */
  private static final String GROUP_PUBLIC_FLG_TRUE = "1";

  /**
   * 組織情報インポート
   * <p>
   * 組織情報のファイルを読み込み、Aipoのデータベースにインポートします。<br>
   * 
   * @param dataContext
   * 
   * @return boolean True(処理成功)、False(処理失敗)
   * @exception DBエラー
   *                、ファイルなし 、データ件数エラーの際に発生
   */
  public boolean importPost(DataContext dataContext) throws Exception {

    int updateCounter = 0;
    int insertCounter = 0;
    int deleteCounter = 0;
    BufferedReader reader = null;

    try {
      // 開始メッセージをログに出力
      logger.info("組織情報更新開始");

      // CSVデータファイル
      File csvFile =
        new File(BatchUtils.IMPORT_CSV_FILE_PATH
          + File.separator
          + POST_FILE_NAME); // データファイル
      if (!csvFile.exists()) {
        throw new FileNotFoundException();
      }

      reader =
        new BufferedReader(new InputStreamReader(
          new FileInputStream(csvFile),
          BatchUtils.FILE_ENCODING));

      List<EipMPost> postList = new ArrayList<EipMPost>();
      List<AipoPost> postFileList = new ArrayList<AipoPost>();

      // 最終行まで読み込み、リスト内に格納する。
      String line = "";
      while ((line = reader.readLine()) != null) {
        String[] s = line.split(BatchUtils.SEPARATOR);
        String userGroupCode = BatchUtils.trimDoubleQuotes(s[0]); // ユーザグループコード
        String userGroupName = BatchUtils.trimDoubleQuotes(s[1]); // ユーザグループ名
        String parentUserGroupName = BatchUtils.trimDoubleQuotes(s[2]); // 親ユーザグループコード
        AipoPost post =
          new AipoPost(userGroupCode, userGroupName.trim(), parentUserGroupName);
        postFileList.add(post);
      }

      // 部署情報ファイルをクローズする。
      reader.close();
      reader = null;

      // <組織情報一覧>の配列に、階層レベルを設定して<組織情報>を追加する。
      List<AipoPost> dstList = getPostHierarchyDataList(postFileList);
      List<String> postAddedList = new ArrayList<String>(); // 処理済みの部署コード格納配列

      for (AipoPost aipoPost : dstList) {

        logger.debug("post code -> " + aipoPost.getUserGroupCode() + ".");

        // 処理済み部署コードに存在するか判定
        // 存在する場合はコード重複エラーとしてロールバック
        if (postAddedList.contains(aipoPost.getUserGroupCode())) {
          throw new Exception("ユーザーグループコードが重複しています。ユーザーグループコード：["
            + aipoPost.getUserGroupCode()
            + "]");
        }

        // 部署コードで部署情報を検索する
        List<EipMPost> resultList =
          BatchUtils.getEipMPostFromPostCode(aipoPost.getUserGroupCode());

        // <部署情報>件数が2件以上の場合、例外を発生させる。
        if (resultList.size() >= 2) {
          throw new Exception("ユーザーグループコードに一致する部署情報が2件以上見つかりました。ユーザーグループコード：["
            + aipoPost.getUserGroupCode()
            + "]");
        }

        EipMPost post = null;

        // <部署情報>件数が1件の場合
        if (resultList.size() == 1) {
          // 部署情報を更新する。
          post = resultList.get(0);
          logger.debug("post update -> " + post.getPostId() + " start.");
          post.setPostName(aipoPost.getUserGroupName());
          post.setUpdateDate(new Date());

          // 階層レベルを判定し、親部署IDを取得
          int parentId = getParentPostId(aipoPost);
          logger.debug("post parent id -> " + parentId + " .");
          post.setParentPostId(parentId);

          logger.debug("group update -> " + post.getGroupName() + " start.");
          // グループを更新
          TurbineGroup group =
            (TurbineGroup) JetspeedSecurity.getGroup(post.getGroupName());
          // グループ名を更新
          group.setGroupAliasName(aipoPost.getUserGroupName());
          dataContext.commitChanges();

          // 更新件数をインクリメントする。
          updateCounter++;
        }

        // <部署情報>件数が0件の場合
        if (resultList.size() == 0) {

          logger.debug("post code -> "
            + aipoPost.getUserGroupCode()
            + " insert start.");

          // 階層レベルを判定し、親部署IDを取得
          int parentId = getParentPostId(aipoPost);
          if (parentId < 0) {
            continue;
          }
          logger.debug("post parent id -> " + parentId + " .");

          // 部署名の重複チェック
          // if (BatchUtils.getEipMPostCount(aipoPost.getUserGroupName()) > 0) {
          // throw new Exception("部署名がすでに登録されています。ユーザーグループ名：["
          // + aipoPost.getUserGroupName()
          // + "]");
          // }

          post = new EipMPost();
          post = Database.create(EipMPost.class);
          post.setPostName(aipoPost.getUserGroupName());
          post.setCompanyId(1);// 会社ID
          post.setCreateDate(new Date());
          post.setUpdateDate(new Date());
          post.setPostCode(aipoPost.getUserGroupCode());
          post.setParentPostId(parentId);

          // グループ名(時間+ユーザIDで一意となるグループ名を作成)
          String groupName =
            new StringBuffer().append(new Date().getTime()).append("_").append(
              aipoPost.getUserGroupCode()).toString();
          post.setGroupName(groupName);

          // グループを追加
          TurbineGroup group = Database.create(TurbineGroup.class);
          group.setGroupName(groupName);
          // オーナID（部署の場合、作成者に依らずuid=1）
          group.setOwnerId(1);
          // グループ名(アプリケーションレベルで付ける名前)
          group.setGroupAliasName(aipoPost.getUserGroupName());
          // 公開フラグ
          group.setPublicFlag(GROUP_PUBLIC_FLG_TRUE);

          logger.debug("group inserted -> " + groupName + " .");

          // 部署順位情報に登録
          List<AvzMPostPosition> posposlist =
            Database.query(AvzMPostPosition.class).fetchList();
          int new_pos =
            (posposlist != null && posposlist.size() > 0)
              ? posposlist.size() + 1
              : 1;
          AvzMPostPosition positionposition =
            Database.create(AvzMPostPosition.class);
          positionposition.setToEipMPost(post);
          positionposition.setPosition(Integer.valueOf(new_pos));

          logger.debug("psot position inserted -> " + new_pos + ".");

          dataContext.commitChanges();
          // 登録件数をインクリメントする。
          insertCounter++;
        }

        // 処理済みの部署コード格納配列に保持
        postAddedList.add(aipoPost.getUserGroupCode());

        // <部署情報レコード>を<部署情報ファイル配列>に追加で格納する。
        postList.add(post);
      }

      logger.debug("post delete start.");
      // 部署情報の全レコードを抽出する。
      SelectQuery<EipMPost> positionQuery = Database.query(EipMPost.class);
      positionQuery.orderAscending(EipMPost.POST_CODE_PROPERTY);
      // 部署情報の削除用リスト
      List<EipMPost> delList = positionQuery.fetchList();

      // 全レコードに対して判定
      for (Iterator<EipMPost> dbPost = delList.iterator(); dbPost.hasNext();) {
        EipMPost post = dbPost.next();
        for (EipMPost filePost : postList) {
          // 部署コードが一致したら削除リストから削除
          if (filePost.getPostId() == post.getPostId()) {
            dbPost.remove();
            break;
          }
        }
      }

      // 削除対象部署リスト
      List<EipMPost> deletePostList = new ArrayList<EipMPost>();
      List<EipMPost> tmpDeleteChildPostList = new ArrayList<EipMPost>();

      // 下位の部署を追加
      for (EipMPost post : delList) {
        deletePostList.add(post);
        if (post.getParentPostId() == 0) {
          // 部署１の場合は、部署２を検索してあれば削除リストに追加する。
          Expression exp =
            ExpressionFactory.matchExp(EipMPost.PARENT_POST_ID_PROPERTY, post
              .getPostId());
          List<EipMPost> childList =
            Database.query(EipMPost.class, exp).fetchList();
          // 部署2のリストを追加する。
          tmpDeleteChildPostList.addAll(childList);
        }
      }

      // 部署2のリストを追加する。
      deletePostList.addAll(tmpDeleteChildPostList);

      // 削除対象部署リストから重複を除く
      deletePostList = removeDuplicates(deletePostList);

      // 削除対象部署リストの部署を順に削除する。
      for (EipMPost e : deletePostList) {
        // グループからユーザーを削除
        List<ALEipUser> users = ALEipUtils.getUsers(e.getGroupName());
        int size = users.size();
        for (int i = 0; i < size; i++) {
          logger.debug("user in group deleted -> " + e.getGroupName() + ".");
          ALBatchGroupUtils.unjoinGroup(users.get(i).getName().getValue(), e
            .getGroupName());
        }
        // グループを削除
        ALBatchGroupUtils.removeGroup(e.getGroupName());
        logger.debug("group deleted -> " + e.getGroupName() + ".");

        // 部署表示順位を削除
        Database.delete(e.getAvzMPostPositionArray());
        logger.debug("post position deleted -> " + e.getPostId() + ".");

        // 部署を削除
        Database.delete(e);
        logger.debug("post deleted -> " + e.getPostId() + ".");

        // 削除件数をインクリメントする。
        deleteCounter++;
      }
      dataContext.commitChanges();

      // 表示順位の更新
      // 部署表示順位情報の一覧を取得
      SelectQuery<AvzMPostPosition> posposQuery =
        Database.query(AvzMPostPosition.class);
      posposQuery.orderAscending(AvzMPostPosition.POSITION_PROPERTY);
      List<AvzMPostPosition> posposList = posposQuery.fetchList();
      int counter = 1;
      for (AvzMPostPosition pospos : posposList) {
        pospos.setPosition(counter);
        counter++;
      }
      dataContext.commitChanges();
      logger.debug("post position updated.");

      // singletonオブジェクトのリフレッシュ
      ALEipManager.getInstance().reloadPost();

      // 終了メッセージ
      logger.info("組織情報更新完了　登録件数：["
        + insertCounter
        + "]　更新件数：["
        + updateCounter
        + "]　削除件数：["
        + deleteCounter
        + "]");

    } catch (FileNotFoundException e) {
      logger.warn("組織情報ファイルが存在しません。");
    } catch (Exception e) {
      try {
        if (reader != null) {
          // 部署情報ファイルをクローズする。
          reader.close();
        }
      } catch (Exception ex) {

      }
      logger.error("組織情報の更新に失敗しました。", e);
      return false;
    }
    return true;
  }

  /**
   * 画面表示用の部署情報リストを取得する （部署１、部署２、部署２、部署１、部署２・・・・・となっている）
   * 
   * @return 部署情報リスト
   */
  public List<AipoPost> getPostHierarchyDataList(List<AipoPost> srcList) {
    List<AipoPost> dstList = new ArrayList<AipoPost>();

    int depth = 0;
    for (AipoPost rd : srcList) {
      // 親ユーザーグループコードがユーザーグループコードと等しい場合は、階層レベルを0とする。
      if (rd.getParentUserGroupCode().equals(rd.getUserGroupCode())) {
        rd.setLevel(depth);
        dstList.add(rd);

        // 子の組織情報に対して再帰的に処理
        getChildren(rd, srcList, dstList, depth);

      }
      // 階層レベルの昇順で配列のソートを行う。
      Collections.sort(dstList, new AipoPostComparatorByLevel());
    }

    return dstList;
  }

  /**
   * 指定された部署（parent）配下の部署をリストに設定する。
   * 
   * @param parent
   *            部署１
   * @param srcList
   *            設定元リスト
   * @param dstList
   *            設定先リスト
   * @param depth
   *            階層
   */
  private static void getChildren(AipoPost parent, List<AipoPost> srcList,
      List<AipoPost> dstList, int depth) {

    depth++;
    for (AipoPost rd : srcList) {
      if (!rd.getUserGroupCode().equals(parent.getUserGroupCode())) {

        // 親ユーザーグループコードがユーザーグループコードと等しい<組織情報>を検索し、
        // 親の組織情報の階層レベル + 1 を階層レベルとする。
        if (rd.getParentUserGroupCode().equals(parent.getUserGroupCode())) {
          rd.setLevel(depth);
          dstList.add(rd);

          getChildren(rd, srcList, dstList, depth);

        }
        // IDの昇順で配列のソートを行う。
        Collections.sort(dstList, new AipoPostComparatorById());
      }
    }
  }

  /**
   * 親部署IDに登録する値の判定を行う。
   * 
   * @param aipoPost
   *            ファイルから読み込んだ組織情報
   * @return int 親部署ID(スキップの場合は-1を返す。)
   * @throws Exception
   */
  private static int getParentPostId(AipoPost aipoPost) throws Exception {
    List<EipMPost> parentPostList = new ArrayList<EipMPost>();

    try {
      switch (aipoPost.getLevel()) {
        case 0: // 第１階層はスキップ
          return -1;
        case 1:// 第２階層
          // 親IDを0でセット
          return 0;
        case 2:// 第３階層

          // 親の部署情報を取得する。
          parentPostList =
            BatchUtils.getEipMPostFromPostCode(aipoPost
              .getParentUserGroupCode());

          // 親部署が見つからない場合はエラーとする。
          if (parentPostList.size() == 0) {
            throw new Exception("親ユーザーグループコードに一致する親部署情報がありません。ユーザーグループコード：["
              + aipoPost.getUserGroupCode()
              + "]");
          }
          // 親IDをセット
          return parentPostList.get(0).getPostId();

        case 3:// 第４階層

          // 部署名に"分室"が含まれていたらデータを登録
          // "分室"が含まれていなかったらスキップ
          if (StringUtils.indexOf(
            aipoPost.getUserGroupName(),
            POST_4TH_LEVEL_REGIST_STRING) < 0) {
            return -1;
          }

          // 親の部署情報を取得する。
          parentPostList =
            BatchUtils.getEipMPostFromPostCode(aipoPost
              .getParentUserGroupCode());

          // 親部署が見つからない場合はエラーとする。
          if (parentPostList.size() == 0) {
            throw new Exception("親ユーザーグループコードに一致する親部署情報がありません。ユーザーグループコード：["
              + aipoPost.getUserGroupCode()
              + "]");
          }

          // さらに親の部署情報を取得する。
          SelectQuery<EipMPost> query = Database.query(EipMPost.class);
          // 部署コードをキーに検索
          Expression exp =
            ExpressionFactory.matchDbExp(
              EipMPost.POST_ID_PK_COLUMN,
              parentPostList.get(0).getParentPostId());
          query.setQualifier(exp);
          List<EipMPost> grandParentPostList = query.fetchList();

          // 親部署が見つからない場合はエラーとする。
          if (grandParentPostList.size() == 0) {
            throw new Exception("親ユーザーグループコードに一致する親部署情報がありません。ユーザーグループコード：["
              + aipoPost.getParentUserGroupCode()
              + "]");
          }
          // 親部署IDを第2階層の部署IDとする。
          return grandParentPostList.get(0).getPostId();
        default:
          return -1;
      }
    } catch (Exception e) {
      throw new Exception(e);
    }

  }

  /**
   * 削除対象部署リストから重複を除く
   * 
   * @param srcList
   *            削除対象部署リスト
   * @return 重複除去後の削除対象部署リスト
   * @see AccountPostMultiDelete#removeDuplicates(List<EipMPost>)
   */
  private List<EipMPost> removeDuplicates(List<EipMPost> srcList) {
    List<EipMPost> destList = new ArrayList<EipMPost>();
    for (EipMPost s : srcList) {
      boolean found = false;
      for (EipMPost d : destList) {
        if (s.getPostId().intValue() == d.getPostId().intValue()) {
          found = true;
          break;
        }
      }
      if (!found) {
        destList.add(s);
      }
    }
    return destList;
  }
}
