import libmime from "libmime";
import libqp from "libqp";

const newline = /\r\n|\r|\n/g;
const SMIMEStart =
  "This is a cryptographically signed message in MIME format.\r\n\r\n";

function findAllOccurences(text, subject, from, to) {
  const result = [];
  let index = text.indexOf(subject, from);
  if (index < 0 || index > to) {
    return result;
  }

  result.push(index + 2);

  while (true) {
    index = text.indexOf(subject, index + 1);
    if (index < 0 || index > to) {
      break;
    } else {
      result.push(index + 2);
    }
  }

  return result;
}

function findFirstBoundary(body, from, to) {
  if (from >= to) {
    return null;
  }
  const search = "\r\n--";

  let start = body.indexOf(search, from);
  if (start < 0 || start > to) {
    return null;
  }

  start += 2;

  const end = body.indexOf("\r\n", start);
  if (end < 0 || end > to) {
    return null;
  }

  const boundary = body.substring(start, end);

  const boundaryEnd = boundary + "--\r\n";

  const startBoundaryEnd = body.indexOf(boundaryEnd, end);

  if (startBoundaryEnd < 0 || startBoundaryEnd > to) {
    return findFirstBoundary(body, end + 2, to);
  }

  return boundary;
}

function calculateParts(body, from, to, previousBondary) {
  let boundary = findFirstBoundary(body, from, to);

  if (boundary == null) {
    return [{ indices: { from,
      to },
    boundary: previousBondary,
    leaf: true }];
  }

  const realBoundary = boundary;
  boundary = "\r\n" + boundary;

  const boundaryPairs = [];

  const boundaryIndices = findAllOccurences(body, boundary, from, to);
  const boundaryIndicesLength = boundaryIndices.length;
  for (let i = 0; i < boundaryIndicesLength; i++) {
    const startBoundary = boundaryIndices[i];
    const endBoundary = body.indexOf("\r\n", startBoundary);
    boundaryPairs.push({ start: startBoundary,
      end: endBoundary });
  }

  let bodies = [];
  if (previousBondary !== null) {
    bodies.push({indices: {from,
      to},
    boundary: previousBondary,
    leaf: false});
  }

  for (let i = 0; i < boundaryIndicesLength - 1; i++) {
    const firstPair = boundaryPairs[i];
    const secondPair = boundaryPairs[i + 1];
    const newFrom = firstPair.end + 2;
    const newTo = secondPair.start - 2;
    const bodyForBoundary = calculateParts(body, newFrom, newTo, realBoundary);
    bodies = bodies.concat(bodyForBoundary);
  }

  return bodies;
}

function parsePartsHeaders(mimeBody, parts) {
  const result = [];
  for (let i = 0; i < parts.length; i++) {
    const part = parts[i];
    const indices = part["indices"];
    let headersEnd = mimeBody
      .substring(indices.from, indices.to)
      .indexOf("\r\n\r\n");
    if (headersEnd < 0) {
      headersEnd = indices.from;
    } else {
      headersEnd = headersEnd + indices.from + 4;
    }
    part["indices"].headersEnd = headersEnd;

    part["headers"] = libmime.decodeHeaders(
      mimeBody.substring(indices.from, headersEnd)
    );
    result.push(part);
  }

  return result;
}

export function fixNewLines(mime) {
  return mime.replace(newline, "\r\n");
}

export function extractMessageID(mime) {
  if (mime.startsWith("\r\n")) {
    mime = mime.substring(2); //should not happen
  }

  const headersEndIndex = mime.indexOf("\r\n\r\n"); //the first empty line
  if (headersEndIndex < 0) {
    return null;
  }
  const mimeHeaders = mime.substring(0, headersEndIndex);
  const headers = libmime.decodeHeaders(mimeHeaders);

  let messageId = headers["message-id"];
  if (Array.isArray(messageId) && messageId.length > 0) {
    messageId = messageId[0];
  }

  if (messageId && typeof messageId === "string") {
    messageId = messageId.replace(/^</, '').replace(/>$/, '');
    return messageId;
  }

  return null;
}

export function parseMIME(mime) {
  let mimeStart = 0;
  const headersEnd = mime.indexOf("\r\n\r\n"); //the first empty line
  let mimeBody = "";
  if (headersEnd < 0 && mime.startsWith("\r\n")) {
    mime = mime.substring(2); //should not happen
    mimeBody = mime;
    mimeStart = 0;
  } else {
    mimeBody = mime.substring(headersEnd + 4);
    mimeStart = headersEnd + 4;
  }

  const headers = libmime.decodeHeaders(mime.substring(0, headersEnd));

  const indexOfSMIME = mimeBody.indexOf(SMIMEStart);

  if (indexOfSMIME >= 0) {
    mimeBody = mimeBody.substring(indexOfSMIME + SMIMEStart.length);
    mimeStart += indexOfSMIME + SMIMEStart.length;
  }

  mimeBody = "\r\n" + mimeBody + "\r\n";
  const mimeBodyLen = mimeBody.length - 1;
  let parts = calculateParts(mimeBody, 0, mimeBodyLen, null);
  parts = parsePartsHeaders(mimeBody, parts);

  for (let i = 0; i < parts.length; i++) {
    parts[i].indices.from = parts[i].indices.from + (mimeStart - 2);
    parts[i].indices.headersEnd = parts[i].indices.headersEnd + (mimeStart - 2);
    parts[i].indices.to = parts[i].indices.to + (mimeStart - 2);
  }

  parts.push({
    indices: { from: 0,
      to: mime.length,
      headersEnd },
    headers,
    boundary: "mimemessage",
    leaf: false
  });

  return parts;
}

export function getHeaderValue(header, part) {
  if (part.headers && part.headers[header] && part.headers[header].length) {
    return part.headers[header];
  }

  return null;
}

export function getGlobalHeaderValue(header, parts) {
  for (let i = 0; i < parts.length; i++) {
    if (parts[i].boundary === "mimemessage") {
      return getHeaderValue(header, parts[i]);
    }
  }

  return null;
}

function getBody(mime, part) {
  if (part.indices === null || part.indices === undefined) {
    return null;
  }

  const indices = part.indices;

  return mime.substring(indices.headersEnd, indices.to);
}

export function decodeMimeBody(descriptor, mimeString) {
  const mimeBody = mimeString.slice(
    descriptor.indices.headersEnd,
    descriptor.indices.to
  );

  let contentTransferEncoding = getHeaderValue(
    "content-transfer-encoding",
    descriptor
  );
  if (contentTransferEncoding) {
    contentTransferEncoding = contentTransferEncoding[0];
  } else {
    return mimeBody;
  }

  let charset = "utf8";
  let contentType = getHeaderValue("content-type", descriptor);
  if (contentType) {
    contentType = contentType[0];
    const parsedContentType = libmime.parseHeaderValue(contentType);
    if (
      parsedContentType &&
      parsedContentType.params &&
      parsedContentType.params.charset
    ) {
      if (parsedContentType.params.charset.toLowerCase() === "us-ascii") {
        charset = "ascii";
      } else if (Buffer.isEncoding(parsedContentType.params.charset)) {
        charset = parsedContentType.params.charset;
      } else {
        //TODO log the charset and make sure we can support it
      }
    }
  }

  if (contentTransferEncoding.toLowerCase() === "quoted-printable") {
    const buff = libqp.decode(mimeBody);
    return buff.toString(charset);
  } else if (contentTransferEncoding.toLowerCase() === "base64") {
    const buff = Buffer.from(mimeBody, "base64");
    return buff.toString(charset);
  }

  return mimeBody;
}

export function getHTML(mime, parts) {
  let html;
  let htmlPart = null;
  for (let i = 0; i < parts.length; i++) {
    if (parts[i].boundary === "mimemessage") {
      continue;
    }
    let contentType = getHeaderValue("content-type", parts[i]);
    if (contentType === null || contentType === undefined) {
      continue;
    }

    contentType = contentType[0];
    if (contentType.indexOf("text/html") >= 0) {
      htmlPart = parts[i];
      break;
    }
  }

  html = decodeMimeBody(htmlPart, mime);

  for (let i = 0; i < parts.length; i++) {
    let contentDisposition = getHeaderValue("content-disposition", parts[i]);
    if (contentDisposition === null || contentDisposition === undefined) {
      continue;
    }
    contentDisposition = contentDisposition[0];
    if (contentDisposition.indexOf("attachment") >= 0 || contentDisposition.indexOf("inline") >= 0) {

      let contentId = getHeaderValue("content-id", parts[i]);
      if (contentId === null || contentId === undefined) {
        continue;
      }

      contentId = contentId[0];
      const contentIdLen = contentId.length;
      contentId = contentId.substring(1, contentIdLen - 1);
      contentId = "cid:" + contentId;
      let contentType = getHeaderValue("content-type", parts[i]);
      if (contentType === null || contentType === undefined) {
        continue;
      }
      contentType = contentType[0].split(";")[0];
      const normalizedBody = getBody(mime, parts[i]).replace(newline, "");
      const src = "data:" + contentType + ";base64, " + normalizedBody;
      html = html.split(contentId).join(src);
    }
  }
  return html;
}

export function getPlain(mime, parts) {
  let plain;
  let plainPart = null;
  for (let i = 0; i < parts.length; i++) {
    let contentType = getHeaderValue("content-type", parts[i]);
    if (contentType === null || contentType === undefined) {
      continue;
    }
    contentType = contentType[0];
    if (contentType.indexOf("text/plain") >= 0) {
      plainPart = parts[i];
      break;
    }
  }
  if (!plainPart) {
    return "";
  }
  plain = decodeMimeBody(plainPart, mime);
  return plain;
}

export function getAttachments(mime, parts) {
  const attachments = [];
  for (let i = 0; i < parts.length; i++) {
    let contentDisposition = getHeaderValue("content-disposition", parts[i]);
    if (contentDisposition === null || contentDisposition === undefined) {
      continue;
    }
    contentDisposition = contentDisposition[0];
    if (contentDisposition.indexOf("attachment") >= 0) {
      attachments.push(parts[i]);
    }
  }

  return attachments;
}

export function getAttachment(mime, part) {
  let contentType = getHeaderValue("content-type", part);
  if (contentType === null || contentType === undefined) {
    return null;
  }
  contentType = contentType[0];
  let contentTransferEncoding = getHeaderValue(
    "content-transfer-encoding",
    part
  );
  if (contentTransferEncoding) {
    contentTransferEncoding = contentTransferEncoding[0];
  } else {
    return null;
  }

  let base64;
  if (contentTransferEncoding.toLowerCase() === "base64") {
    base64 = getBody(mime, part).replace(newline, "");
  } else {
    const body = decodeMimeBody(part, mime);
    base64 = window.btoa(body);
  }

  return { contentType,
    base64 };
}