import java.io.*;
import java.util.*;
import java.text.*;
import java.nio.charset.*;

class VCard implements Comparable<VCard> {

  static String[] ttel = new String[] { "CELL", "HOME", "WORK" };
  static String[] tadr = new String[] { "HOME", "WORK" };
  static String[] imgf = new String[] { "JPEG", "PNG", "GIF" };
  Charset charset;
  int specify;
  String fname = null, org = null, title = null, note = null;
  String[] name;
  byte[] photo = null;
  int itphoto = 0;
  Object obj;
  final List<Itm> tel = new ArrayList<>(), adr = new ArrayList<>(),
                email = new ArrayList<>(), url = new ArrayList<>();
  int ptel = -1, padr = -1, pemail = -1, purl = -1;
  Date bday = null, rev = null;
  @SuppressWarnings({ "rawtypes", "unchecked" })
  final List<Itm>[] al = (List<Itm>[]) new List[] { tel, adr, email, url };

  //VCard() { }

  @Override
  public int compareTo(VCard vc) {
    String s1 = (fname == null ? "" : fname), s2 = (vc.fname == null ? "" : vc.fname);
    return s1.compareToIgnoreCase(s2);
  }

  @Override
  public String toString() {
    String st = "VCard" + "\n";
    if (fname != null) st += "FN: " + fname + "\n";
    if (name != null) st += "N[]: " + String.join(",", name) + "\n";
    if (bday != null) st += "BDAY: " + DateFormat.getDateInstance(DateFormat.SHORT).format(bday) + "\n";
    if (org != null) st += "ORG: " + org + "\n";
    if (title != null) st += "TITLE: " + title + "\n";
    if (photo != null) st += "PHOTO: " + imgf[itphoto < 0 || itphoto >= imgf.length ? 0 : itphoto] + " " + photo.length + " Bt\n";
    for (List<Itm> litm : al) for (int i = 0; i < litm.size(); i++) if (litm.get(i) != null) {
      if (litm == tel)
        st += "TEL: " + (String)tel.get(i).value + " " + ttel[tel.get(i).idl < 0 || tel.get(i).idl >= ttel.length ? 0 : tel.get(i).idl] + (ptel == i ? " PREF" : "");
      else if (litm == adr)
        st += "ADR[]: " + String.join(",", (String[])adr.get(i).value).replace('\n', ' ') + " " + tadr[adr.get(i).idl < 0 || adr.get(i).idl >= tadr.length ? 0 : adr.get(i).idl] + (padr == i ? " PREF" : "");
      else if (litm == email)
        st += "EMAIL: " + (String)email.get(i).value + (pemail == i ? " PREF" : "");
      else if (litm == url)
        st += "URL: " + (String)url.get(i).value + (purl == i ? " PREF" : "");
      st += " " + litm.get(i).note.replace('\n', ' ') + "\n";
    }
    if (note != null) st += "NOTE: " + note.replace('\n', ' ') + "\n";
    if (rev != null) st += "REV: " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(rev) + "\n";
    return st;
  }

  boolean contains(String st) {
    st = st.toUpperCase();
    if ((fname != null && fname.toUpperCase().indexOf(st) > -1) ||
        (org != null && org.toUpperCase().indexOf(st) > -1) ||
        (title != null && title.toUpperCase().indexOf(st) > -1) ||
        (note != null && note.toUpperCase().indexOf(st) > -1) ||
        (bday != null && DateFormat.getDateInstance(DateFormat.SHORT).format(bday).indexOf(st) > -1) ||
        (rev != null && DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(rev).indexOf(st) > -1)) return true;
    if (name != null) for (String s: name) if (s != null && s.toUpperCase().indexOf(st) > -1) return true;
    String stt = ctel(st);
    for (List<Itm> litm : al) for (Itm itm : litm) if (itm != null) {
      if (itm.note != null && itm.note.toUpperCase().indexOf(st) > -1) return true;
      if (itm.value != null) {
        if (litm == adr) for (String s : (String[])itm.value) { if (s != null && s.toUpperCase().indexOf(st) > -1) return true; }
        else if (((String)itm.value).toUpperCase().indexOf(st) > -1) return true;
      }
      if (litm == tel && !stt.isEmpty()) if (ctel((String)itm.value).indexOf(stt) > -1) return true;
    }
    return false;
  }

  String ctel(String st) {
    StringBuilder sb = new StringBuilder();
    char ch;
    for (int i = 0; i < st.length(); i++) {
      ch = st.charAt(i);
      if ((ch >= '0' && ch <= '9') || ch == '+') sb.append(ch);
    }
    return sb.toString();
  }

  static BufferedInputStream bis;
  static int ch;

  static List<VCard> read(InputStream in, Charset cs) throws IOException {
    VCard.bis = new BufferedInputStream(in, 1024);
    Map<String, String> mNote = new HashMap<>();
    int b, e, ind;
    boolean b64, qp, pref;
    int itel, iadr, iimgf;
    byte[] bt;
    Charset scs;
    String st, mc, m;
    if (!"BEGIN:VCARD".equals(new String(bis.readNBytes(11), StandardCharsets.ISO_8859_1))) throw new IOException("Format error");
    if (cs == null) cs = Charset.defaultCharset();
    List<VCard> lst = new ArrayList<>();
    VCard vc = new VCard();
    ch = -1;
    do {
      st = readk();
      if (st == null) continue;
      b64 = qp = pref = false;
      itel = iadr = iimgf = -1;
      scs = null;
      bt = null;
      m = "";
      b = 0;
      mc = null; 
      do {
          e = st.indexOf(';', b);
          String prm = st.substring(b, (e < 0 ? st.length() : e)).strip();
          if (b == 0) {
            ind = prm.lastIndexOf('.');
            if (ind >= 0) { m = prm.substring(0, ind); prm = prm.substring(ind + 1); }
            mc = prm;
          } else {
            if (!qp && prm.endsWith("QUOTED-PRINTABLE")) qp = true;
            if (!b64 && prm.endsWith("BASE64")) b64 = true;
            if (itel == -1) itel = arrs(ttel, prm);
            if (iadr == -1) iadr = arrs(tadr, prm);
            if (iimgf == -1) iimgf = arrs(imgf, prm);
            if (scs == null && prm.startsWith("CHARSET=")) {
              try { scs = Charset.forName(prm.substring(8)); } catch (Exception ex) { }
            }
            if (!pref && prm.indexOf("PREF") >= 0) pref = true;
          }  
          b = e + 1;
      } while (e >= 0);
      if ("END".equals(mc) && "VCARD".equals(new String(readv(), StandardCharsets.ISO_8859_1))) {
        for (List<Itm> litm : vc.al)
          for (Itm itm : litm) if (!itm.m.isEmpty()) { String s = mNote.get(itm.m); if (s != null) itm.note = s; };
        lst.add(vc);
        mNote.clear();
        vc = new VCard();
        continue;
      }
      if (scs == null) scs = cs;
      if (b64) { bt = readb64(); }
      else if (qp) { bt = readqp(); }
      else { bt = readv(); }
        if ("FN".equals(mc)) vc.fname = new String(bt, scs);
        else if ("N".equals(mc)) vc.name = arrf(bt, 5, scs);
        else if ("ORG".equals(mc)) vc.org = new String(bt, scs);
        else if ("TITLE".equals(mc)) vc.title = new String(bt, scs);
        else if ("BDAY".equals(mc)) vc.bday = parseDate(new String(bt, StandardCharsets.ISO_8859_1));
        else if ("REV".equals(mc)) vc.rev = parseDate(new String(bt, StandardCharsets.ISO_8859_1));
        else if ("NOTE".equals(mc)) {
          if (m.isEmpty()) vc.note = new String(bt, scs);
          else { mNote.put(m, new String(bt, scs)); }
        } else if ("PHOTO".equals(mc)) {
          vc.photo = bt;
          vc.itphoto = (iimgf < 0 ? 0 : iimgf);
        } else if ("TEL".equals(mc)) {
          vc.tel.add(new Itm(m, (itel < 0 ? 0 : itel), new String(bt, scs) ,""));
          if (pref) vc.ptel = vc.tel.size() - 1;
        } else if ("EMAIL".equals(mc)) {
          vc.email.add(new Itm(m, -1, new String(bt, scs), ""));
          if (pref) vc.pemail = vc.email.size() - 1;
        } else if ("URL".equals(mc)) {
          vc.url.add(new Itm(m, -1, new String(bt, scs), ""));
          if (pref) vc.purl = vc.url.size() - 1;
        } else if("ADR".equals(mc)) {
          vc.adr.add(new Itm(m, (iadr < 0 ? 0 : iadr), arrf(bt, 7, scs), ""));
          if (pref) vc.padr = vc.adr.size() - 1;
        }
    } while (ch != -1);
    return lst;
  }

  static int arrs(String arr[], String st) {
    for (int i = 0; i < arr.length; i++) if (st.endsWith(arr[i])) return i;
    return -1;
  }

  static String[] arrf(byte[] bt, int sz, Charset cs) {
    String[] ms = new String[sz];
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    boolean b = false;
    int c = 0;
    for (int i = 0; i < bt.length; i++) {
      if (!b && bt[i] == '\\') { b = true; continue; }
      if (b) b = false;
      else if (bt[i] == '\\') continue;
      else if (bt[i] == ';') {
        if (c >= ms.length - 1) break;
        ms[c++] = baos.toString(cs);
        baos.reset();
        continue;
      }
      baos.write(bt[i]);
    }
    ms[c] = baos.toString(cs);
    return ms;
  }

  static String readk() throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    while((ch = bis.read()) != -1) {
      if (ch == '\n') return null;
      if (ch == ':') break;
      if (ch > 0x20) baos.write((byte) ch);
    }
    return (baos.size() == 0 ? null : baos.toString(StandardCharsets.ISO_8859_1).toUpperCase());
  }

  static byte[] readv() throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    while((ch = bis.read()) != -1) {
      if (ch == '\n') {
 	bis.mark(10);
        if ((ch = bis.read()) == -1) break;
        if (ch == 0x20) continue;
        bis.reset();
        break;
      }
      if (ch != '\r') baos.write((byte) ch);
    }
    return baos.toByteArray();
  }

  static byte[] readb64() throws IOException {
    byte[] bt = readv();
    try {
      return Base64.getDecoder().decode(bt);
    } catch (Exception ex) { return null; }
  }

  static byte[] readqp() throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int c = 0, u = -1, l;
    while((ch = bis.read()) != -1) {
      if (ch >= 0x20 || ch == '\n' || ch == '\t') {
        if (ch == '=') { c = 1; continue; } 
        if (c == 0 && ch == '\n') break; 
        if (c == 1 && ch == '\n') { c = 0; continue; } 
        if (c == 1) { c = 2; u = Character.digit((char) ch, 16); continue; } 
        if (c == 2) { c = 0; l = Character.digit((char) ch, 16);
          if (u == -1 || l == -1) continue;
          ch = ((u << 4) + l);
          if (ch < 0x20 && ch != '\n' && ch != '\t') continue;
        } 
        baos.write((byte) ch);
      }
    }
    return baos.toByteArray();
  }

  static Date parseDate(String source) {
    source = source.replace("-", "").replace(":", "").toUpperCase();
    String pattern;
    if (source.length() > 14) { source = source.substring(0, 15); pattern = "yyyyMMdd'T'HHmmss"; }
    else pattern = "yyyyMMdd";
    try {
      return new SimpleDateFormat(pattern).parse(source);
    } catch (Exception e) { return null; }
  }

  final int size = 75;
  OutputStream out;

  static void write(OutputStream out, List<VCard> lst, Charset charset, int specify) throws IOException {
    for (VCard vc : lst) vc.write(out, charset, specify);
  }

  void write(OutputStream out, Charset charset, int specify) throws IOException {
    this.out = out;
    this.charset = charset;
    this.specify = specify;
    String dptrn1 = "yyyyMMdd";
    String dptrn2 = "yyyyMMdd'T'HHmmss'Z'";
    int n = 1;
    String s, mrk = "M";
    boolean b;
    appln("BEGIN:VCARD");
    appln("VERSION:2.1");
    app("FN", fname, true);
    app("N", name, 5, true);
    if (bday != null) appln("BDAY:" + new SimpleDateFormat(dptrn1).format(bday));
    app("ORG", org, false);
    app("TITLE", title, false);
    for (List<Itm> litm : al) for (int i = 0; i < litm.size(); i++) {
      s = ((b = !litm.get(i).note.isEmpty()) ? mrk + (n++) + "." : "");
      if (litm == tel)
        app(al(s + "TEL", ttel, tel.get(i).idl, (i == ptel)), (String)tel.get(i).value, b);
      else if (litm == email)
        app(al(s + "EMAIL", null, 0, (i == pemail)), (String)email.get(i).value, b);
      else if (litm == url)
        app(al(s + "URL", null, 0, (i == purl)), (String)url.get(i).value, b);
      else if (litm == adr)
        app(al(s + "ADR", tadr, adr.get(i).idl, (i == padr)), (String[])adr.get(i).value, 7, b);
      if (b) app(s + "NOTE", litm.get(i).note, false);
    }
    if (photo != null && photo.length != 0) {
      String st = "PHOTO;TYPE="+ imgf[itphoto < 0 || itphoto >= imgf.length ? 0 : itphoto] +";ENCODING=BASE64:" + Base64.getEncoder().encodeToString(photo);
      appln(st.substring(0, Math.min(st.length(), size + 1)));
      for (int index = size + 1; index < st.length(); index += size)
        appln(" " + st.substring(index, Math.min(st.length(), index + size)));
      out.write('\r');
      out.write('\n');
    }
    app("NOTE", note, false);
    if (rev != null) appln("REV:" + new SimpleDateFormat(dptrn2).format(rev));
    appln("END:VCARD");
  }

  String al(String st, String[] ms, int i, boolean b) {
    StringBuilder sb = new StringBuilder(st);
    if (ms != null) sb.append(';').append(ms[(i < 0 || i >= ms.length ? 0 : i)]);
    if (b) sb.append(";PREF");
    return sb.toString();
  }

  void appln(String st) throws IOException {
    out.write(st.getBytes(StandardCharsets.ISO_8859_1));
    out.write('\r');
    out.write('\n');
  }

  void app(String st, String s, boolean required) throws IOException {
    app(st, new String[] { s }, 1, required);
  }

  void app(String st, String[] ms, int len, boolean required)  throws IOException {
    boolean ascii = true, empty = true, singleline = true;
    StringBuilder sb = new StringBuilder();
    for (int j = 0; j < len; j++) {
      if (j > 0) sb.append(';');
      String s = (ms != null && j < ms.length && ms[j] != null ? ms[j] : "");
      int end = s.length() - 1;
      while (end >= 0 && s.charAt(end) == 0x20) end--;
      for (int i = 0; i <= end; i++) {
        char ch = s.charAt(i);
        if (ch >= 0x20 || ch == '\n' || ch == '\t') {
          if (len > 1 && (ch == '\\' || ch ==';')) sb.append('\\');
          if (ascii && ch > 0x7e) ascii = false;
          if (singleline && ch == '\n') singleline = false;
          if (empty) empty = false;
          sb.append(ch);
        }
      }
    }
    if (required || !empty) {
      byte[] bt = sb.toString().getBytes(charset);
      sb.setLength(0);
      int spc = specify;
      if (ascii) spc = 2;
      if (!singleline) spc = 0;
      sb.append(st);
      if (spc < 2) sb.append(";CHARSET=").append(charset.name().toUpperCase());
      if (spc < 1) sb.append(";ENCODING=QUOTED-PRINTABLE");
      sb.append(':');
      if (spc < 1) {  // 0-Charset+QP, 1-Specified, 2-Not specified
        for (int cnt = sb.length(), i = 0; i < bt.length; i++) {
          int ch = (bt[i] & 0xFF);
          if (cnt >= size) { sb.append("=\r\n"); cnt = 0; }
          if (ch == '\n') {
            sb.append("=0D=0A"); cnt = 0;
            if (bt.length - i > 1) sb.append("=\r\n");
          } else if (ch >= 0x20 && ch <= 0x7e && ch != '=') {
            sb.append((char)ch); cnt++;
          } else {
            sb.append(String.format("=%02X", ch)); cnt += 3;
          }
        }
        appln(sb.toString());
      } else {
        out.write(sb.toString().getBytes(StandardCharsets.ISO_8859_1));
        out.write(bt);
        out.write('\r');
        out.write('\n');
      }
    }
  }

  static class Itm {

    int idl;
    String m, note;
    Object value;

    Itm(String m, int idl, Object value, String note) { this.m = m; this.idl = idl; this.value = value; this.note = note; }

    @Override
    public String toString() { return "Itm " + (value instanceof String ? (String) value : value instanceof String[] ? Arrays.toString((String[]) value) : ""); }
  }

}