#!/usr/local/bin/perl
$iCalCalendar = "Library/Calendars/official.ics"; # $HOME 以下の場所
#
# iCal (by Apple, Sep. 2002) の出力(vCalendar形式)をNeoJapanのDesknet'sが使う
# CSVの形式に変換するフィルタ
#
# Syntax:
#   $0 dnet_file
#       $0        - このスクリプト
#       dnet_file - デスクネットからエクスポートしたCSVファイル
#
# 対象： iCal 1.0 および Desknet 1.0 R0.4
#
# インポートするファイルのフォーマット：
# ユーザーシステムID,氏名,ＩＤ（システムＩＤ：自動発番）,開始日,開始時刻,
# 終了日,終了時刻,予定,予定詳細,場所,場所詳細,内容,情報公開レベル,外出区分,
# 重要度,予約種別,帯状,フラグ,アイコン番号
#
# エクスポートするファイルのフォーマット：
# 上のフォーマットから、行頭の二つのフィールドがなくなったもの
#
# ユーザーシステムＩＤ、氏名は dnet_file から読みとる。しかし出力には関係ない。
# システムＩＤは新規イベントについては 0 にする。
#
# Dnetエントリー       ここでの変数       iCalでのエントリー
# StrtDay, StrtTime <- $state, $stime  <- DTSTART
# EndDay, EndTime   <- $edate, $etime  <- DTENT
# Event             <- $content        <- SUMMARY
# Detail            <- $comment        <- DESCRIPTION
#
# vCalendar の BEGIN:VEVENT 行から END:VEVENT 行の間を、適当に整形する。
# ほかの部分は無視する。Dnetのほかのエントリーも空フィールドにする。
#
# イベント名は SUMMARY で指定されたものをそのまま出力する。
#
# DESCRIPTION で指定されている文字列は\n（改行ではなく、\ と n）で結合されて
# いるが、iCalのメモ欄の複数の行である。各行をそれぞれ（）ではさんでイベント
# 名の後に出力する。行が URL と思われる文字列の場合（行頭あるいは\nの直後が
# http://の場合）は、イベント名からHTMLタグでリンクを張る。
#
# DTSTART が文字 T をはさんで年月日８桁と時刻６桁であるとして、T よりも後が
# ある時には [時刻]（Tの直後４桁）をイベント名の前につける。なければつけない。
#       20020920        -> 2002年9月20日
#       20020920T123000 -> 2002年9月20日12時30分
# 終日イベントは、時刻の指定がなく、DTSTARTとDTENDが一日ずれてることで表現され
# ている。時刻は開始あるいは終了のどちらかだけが指定されていることはない、と
# する。
#
# 理解するタグは全部で６種類のみ。
#       BEGIN, END, SUMMARY, DTSTART, DTEND, DESCRIPTION.
# このうち BEGIN と END に関しては、VEVENT しか理解しない。
#
#
# 検証環境
#   Mac OS X 10.2 および perl 5.8.0
#
# Sep. 24, 2002 TOMINAGA Daisuke
#
# TODO
#   予定は全部上書き、ということにしてしまうと楽かもしれない。ダウンロードした
#   ファイルからイベントのＩＤを抜き出し、それをiCalから抽出したイベントに全て
#   割り振ってしまう。
#

use 5.8.0;
use Switch;
use Encode;
use Encode qw(from_to);

# Desknet's Calendar からの情報抽出 ###########################################
# とりあえず全部読み込んで、Unicode にして配列 @reserved に入れておく
# １行目は各フィールドの説明、２行目にだけユーザーIDとユーザー名が書いてある

$dnetCalendar = $ARGV[0]; # 第一引数で指定されていなければならない。

if (open(DNET, $dnetCalendar)) {
  while (<DNET>) {
    s|\r\n$||;
    from_to($_, 'shift-jis', 'utf8');
    @reserved = (@reserved, $_);
  }
  # ２行目からユーザーIDとユーザー名を取り出す
  ($UserSystemID, $UserName) = split(/,/, $reserved[1]);
  close(DNET); # Desknet ファイルの読み込みは終了
} else {
  print "User system ID on desknet: "; $UserSystemID = <STDIN>;
  print "User name on desknet:      "; $UserNmae = <STDIN>;
  chop($UserSystemID); chop($UserName);
}

# iCal Calendar からのイベント情報抽出と整形 ##################################
# イベント抽出、イベント整形を１セットにして繰り返す
# 抽出したイベント情報は、desknet's の形式にして１イベント１行にまとめて、
# @events に入れる

# iCal のカレンダーファイルをまずオープン。ここから読みとる。
$iCalCalendar = $ENV{'HOME'} . "/" . $iCalCalendar;
open(ICAL, $iCalCalendar) || die("iCal calendar? ->" . $iCalCalendar . "\n");

while (<ICAL>) {
  if (/^BEGIN:VEVENT/) {             # イベント定義に出会ったら抽出と整形をやる
    $content = $sdate = $edate = $comment = "";
    # まずイベント抽出（イベントの内容を変数に格納する）
    while (<ICAL>) {
      s|\n$||;                       # 入力行の行末の改行を削除

      # 入力行のチェック
      if (/^END:VEVENT/) { last; }   # イベント定義終了、次のイベント定義を探す
      if (/^ +/) {                   # 前のDESCRIPTION行の続きだから
        s|^ +||;                     # 入力行の先頭の空白を削除
        $comment = $comment . $_;    # 前に抽出していたDESCRIPTIONに追加
        next;                        # あとはとばして次の入力行へ
      }

      # 入力行の解析
      ($tag) = split(":");           # 行に含まれる最初の : までがタグ
      s/^[^:]+://; $val = $_;        # それ以降は、すべてデータ（:もあり得る）

      # 何を定義しているかによって、その内容を所定の変数に入れる
      switch ($tag) {
        case /SUMMARY/     { $content = $val; }  # イベント名
        case /DTSTART/     { $sdate   = $val; }  # 開始日時
        case /DTEND/       { $edate   = $val; }  # 終了日時
        case /DESCRIPTION/ { $comment = $val; }  # なんかコメントなど
      }
    }

    # イベント整形
    &comment;                                  # iCal のメモ欄の整形

    if ($sdate eq "") { $sdate = "19700101"; } # 本来、指定がなければエラー
    if ($edate eq "") { $edate = $sdate }
    # 開始日時のフォーマット整形
    $sdate =~ /^([0-9]{4})/;         $y = $1;  # 年の抽出 20020920 -> 2002
    $sdate =~ /^[0-9]{4}([0-9]{2})/; $m = $1;  # 月の抽出 20020920 -> 09
    $sdate =~ /^[0-9]{6}([0-9]{2})/; $d = $1;  # 日の抽出 20020920 -> 20
    # 時刻の指定があった場合
    if ($sdate =~ /T([0-9]{2})([0-9]{2})..$/) { $stime = $1.":".$2; }
    else                                      { $stime = ""; }
    $sdate = $y . "/" . $m . "/" . $d;          # -> 2002/09/20

    # 今度は終了日時
    $edate =~ /^([0-9]{4})/;         $y = $1;
    $edate =~ /^[0-9]{4}([0-9]{2})/; $m = $1;
    $edate =~ /^[0-9]{6}([0-9]{2})/; $d = $1;
    if ($edate =~ /T([0-9]{2})([0-9]{2})..$/) { $etime = $1.":".$2; }
    else                                      { $etime = ""; }
    # 終日イベントの処理
    if ($stime eq "") { $edate = $sdate; }     # 時刻の指定がないとき
    else              { $edate = $y . "/" . $m . "/" . $d; }

    $e = "0";                                  # システムＩＤ（新規は 0）
    for ($i = 4; $i <= 17; $i++) { # フィールド総数は１７個
      switch ($i) {
        case  4 { $e = $e . "," . $sdate;    } # 開始日
        case  5 { $e = $e . "," . $stime;    } # 開始時刻
        case  6 { $e = $e . "," . $edate;    } # 終了日
        case  7 { $e = $e . "," . $etime;    } # 終了時刻
        case  8 { $e = $e . "," . $content;  } # 予定
        case  9 { $e = $e . ",";             } # 予定詳細
        case 10 { $e = $e . ",";             } # 場所
        case 11 { $e = $e . ",";             } # 場所詳細
        case 12 { $e = $e . "," . $comment;  } # 内容
        case 13 { $e = $e . ",";             } # 情報公開レベル
        case 14 { $e = $e . ",";             } # 外出区分
        case 15 { $e = $e . ",";             } # 重要度
        case 16 { $e = $e . ",";             } # 予約種別
        case 17 { $e = $e . ",";             } # 帯状
        case 18 { $e = $e . ",";             } # フラグ
        case 19 { $e = $e . ",";             } # アイコン番号
      }
    }

    @events = ($e, @events);
  }
}
@events = sort by_date @events;

close(ICAL); # iCalファイルからの読み込みは終了

# イベントの重複を調べて出力 ##################################################
# 開始＆終了の日時と予定と内容が同じものは重複と見なす。
# また、連続しているイベントは、日付を調整して帯状にするべき。

# まず、重複を調べる。@events の各要素について @reserved の各要素と比較する
foreach $e (@events) {
  # 開始日、開始時刻、終了日、終了時刻、予定、...、内容を取り出す
#  ($a,$b,$c, $sd, $st, $ed, $et, $ev, $d,$f,$g, $ct) = split(/,/, $e);
  ($c, $sd, $st, $ed, $et, $ev, $d,$f,$g, $ct) = split(/,/, $e);
  $matched = 0;
  foreach $r (@reserved) {           # iCalデータとDesknetデータを比較
    # Desknet のエントリーからも取り出す
    ($a,$b,$c, $rsd, $rst, $red, $ret, $rev, $d,$f,$g, $rct) = split(/,/, $r);
    # iCalデータと比較
    if (($sd eq $rsd) && ($st eq $rst) && ($ed eq $red) &&
        ($et eq $ret) && ($ev eq $rev) ) {
      $matched = 1;                  # 同じものがあったら、フラグをたてる
      $tmp = $r;
      last;
    }
  }
  if ($matched == 0) {               # Desknet になかったものだけ集める
    @newevents = (@newevents, $e);
  }
}


# @events の内容を、@newevents + @reserved にする。
@events = (@newevents, @reserved);

# 日程の連続するイベントを調べる。
foreach $e (@events) {
  ($c, $sd, $st, $ed, $et, $ev, $d,$f,$g, $ct) = split(/,/, $e);
  @slist = ($e);
  foreach $r (@events) {             # 内容が一致するものを探す
    ($c, $rsd, $rst, $red, $ret, $rev, $d,$f,$g, $rct) = split(/,/, $r);
    if ($ev eq $rev) { # あったらピックアップ
      @slist = (@slist, $r);
    }
  }
  if ($slist > 1) {                  # 内容が一致するものがあったら
    @slist = sort by_date @slist;    # とりあえずソート
    for ($i = 1; $i < $slist; $i++) {
    }
  }
}

# イベント書き出し
# 新しいヤツだけ出力する。
foreach $n (@newevents) {
#  from_to($n, 'utf8', 'euc-jp');
  from_to($n, 'utf8', 'shiftjis');
  printf "%s\n",  $n;
}

# main loop 終了 ##############################################################

# コメント処理
# iCalのメモの部分を $comment に入れる
# URLだったら、なんかリンクを張る
sub comment {
  $comstr = "";
  @clist = split(m|\\n|, $comment);  # 文字列 \n で分割
  foreach (@clist) {
    s|\n$||;                         # 行末の改行を削除
    if (/^$/)  { next; }             # 元々空行のところだったらスキップ
    s|\\"|"|g;                       # \" をただの " に置き換え
    if (m|^http://|) {
      s| ||g;                        # 空白文字の入ったURLはないものと期待
      $comstr = $comstr . "<A href=\"" . $_ . "\">URL</A>";
    } else {
      $comstr = $comstr . $_ . ". ";
    }
  }
  $comstr =~ s|,|\.|g;               # CSV に書き出すから
  $comment = $comstr;
}

# イベントのソート用の比較関数
# 開始日で比較し、同じだったら開始時刻で比較する
sub by_date {
  ($x, $date_a, $time_a) = split(/,/, $a);
  ($x, $date_b, $time_b) = split(/,/, $b);

  if      ($date_a le $date_b ) { -1; }
  elsif   ($date_a gt $date_b ) {  1; }
  elsif   ($date_a eq $date_b ) {
    if    ($time_a le $time_b)  { -1; }
    elsif ($time_a gt $time_b)  {  1; }
    else                        {  0; }
  }
}
