import { multistatus } from './parser';
import * as template from './template';

/**
 * Options:
 *
 *   (String) depth - optional value for Depth header.
 *   (Array.<Object>) props - list of props to request.
 */
export function addressBookQuery(options) {
  return collectionQuery(
    template.addressBookQuery({ props: options.props || [] }),
    { depth: options.depth }
  );
}

/**
 * Options:
 *
 *   (String) data - put request body.
 *   (String) method - http method.
 *   (String) etag - cached calendar object etag.
 */
export function basic(options) {
  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  return new Request({
    method: options.method,
    requestData: options.data,
    transformRequest: transformRequest
  });
}

/**
 * Options:
 *
 *   (String) depth - optional value for Depth header.
 *   (Array.<Object>) filters - list of filters to send with request.
 *   (Array.<Object>) props - list of props to request.
 *   (String) timezone - VTIMEZONE calendar object.
 */
export function calendarQuery(options) {
  return collectionQuery(
    template.calendarQuery({
      props: options.props || [],
      filters: options.filters || [],
      timezone: options.timezone
    }),
    {
      depth: options.depth
    }
  );
}

export function collectionQuery(requestData, options) {
  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  function transformResponse(xhr) {
    return multistatus(xhr.responseText).response.map(res => {
      return { href: res.href, props: getProps(res.propstat) };
    });
  }

  return new Request({
    method: 'REPORT',
    requestData: requestData,
    transformRequest: transformRequest,
    transformResponse: transformResponse
  });
}

export function mkcol(options) {
  let requestData = template.mkcol({ props: options.props });

  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  return new Request({
    method: 'MKCOL',
    requestData: requestData,
    transformRequest: transformRequest
  });
}

export function proppatch(options) {
  let requestData = template.proppatch({ props: options.props });

  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  return new Request({
    method: 'PROPPATCH',
    requestData: requestData,
    transformRequest: transformRequest
  });
}

/**
 * Options:
 *
 *   (String) depth - optional value for Depth header.
 *   (Array.<Object>) props - list of props to request.
 */
export function propfind(options) {
  let requestData = template.propfind({ props: options.props });

  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  function transformResponse(xhr) {
    let responses = multistatus(xhr.responseText).response.map(res => {
      return { href: res.href, props: getProps(res.propstat) };
    });

    if (!options.mergeResponses) {
      return responses;
    }

    // Merge the props.
    let merged = mergeProps(responses.map(res => res.props));
    let hrefs = responses.map(res => res.href);
    return { props: merged, hrefs: hrefs };
  }

  return new Request({
    method: 'PROPFIND',
    requestData: requestData,
    transformRequest: transformRequest,
    transformResponse: transformResponse
  });
}

/**
 * Options:
 *
 *   (String) depth - option value for Depth header.
 *   (Array.<Object>) props - list of props to request.
 *   (Number) syncLevel - indicates scope of the sync report request.
 *   (String) syncToken - synchronization token provided by the server.
 */
export function syncCollection(options) {
  let requestData = template.syncCollection({
    props: options.props,
    syncLevel: options.syncLevel,
    syncToken: options.syncToken
  });

  function transformRequest(xhr) {
    setRequestHeaders(xhr, options);
  }

  function transformResponse(xhr) {
    let object = multistatus(xhr.responseText);
    let responses = object.response.map(res => {
      return { href: res.href, props: getProps(res.propstat) };
    });

    return { responses: responses, syncToken: object.syncToken };
  }

  return new Request({
    method: 'REPORT',
    requestData: requestData,
    transformRequest: transformRequest,
    transformResponse: transformResponse
  });
}

export class Request {
  constructor(options={}) {
    Object.assign(this, {
      method: null,
      requestData: null,
      transformRequest: null,
      transformResponse: null,
      onerror: null
    }, options);
  }
}

function getProp(propstat) {
  if (/404/g.test(propstat.status)) {
    return null;
  }
  if (/5\d{2}/g.test(propstat.status) ||
      /4\d{2}/g.test(propstat.status)) {
    throw new Error('Bad status on propstat: ' + propstat.status);
  }

  return ('prop' in propstat) ? propstat.prop : null;
}

export function mergeProps(props) {
  return props.reduce((a, b) => Object.assign(a, b), {});
}

/**
 * Map propstats to props.
 */
export function getProps(propstats) {
  return mergeProps(
    propstats
      .map(getProp)
      .filter(prop => prop && typeof prop === 'object')
  );
}

export function setRequestHeaders(request, options) {
  request.setRequestHeader('Content-Type', 'application/xml;charset=utf-8');

  if ('depth' in options) {
    request.setRequestHeader('Depth', options.depth);
  }

  if ('etag' in options) {
    request.setRequestHeader('If-Match', options.etag);
  }

  if ('destination' in options) {
    request.setRequestHeader('Destination', options.destination);
  }

  if ('overwrite' in options) {
    request.setRequestHeader('Overwrite', options.overwrite);
  }
}
