'use strict';

const {
  DragDropContext,
  Draggable,
  Droppable
} = window.ReactBeautifulDnd;

const data = window.shop_data;


const productStyle = (style, snapshot, removeAnim, hovered, selected) => {
  const custom = {
    opacity: snapshot.isDragging ? .7 : 1,
    backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial',
  };

  if (!snapshot.isDropAnimating) {
    return { ...style, ...custom};
  }

  if (removeAnim) {
    // cannot be 0, but make it super tiny
    custom.transitionDuration = '0.001s';
  }

  return {
    ...style,
    ...custom,
  };
}

const cartStyle = (style, snapshot) => {
  const isDraggingOver = snapshot.isDraggingOver;
  return {
    ...style,
    ...{
      backgroundColor: isDraggingOver ? '#f2f2f2' : '#f9f9f9',
      border: isDraggingOver ? '1px dashed #ccc' : '0',
    },
  };
}

const nbrConnectorsStyle = (data) => {
  if (!data || !data.nbrCurrentSlot) {
    return {};
  }

  let p = data.nbrCurrentSlot * 100 / data.nbrSlotMax;
  if (p > 100) {
    p = 100;
  }
  return {
    width: `${p}%`,
  }
};

const nbrClocksStyle = (data) => {
  if (!data || !data.nbrCurrentClock) {
    return {};
  }

  let p = data.nbrCurrentClock * 100 / data.nbrClockMax;
  if (p > 100) {
    p = 100;
  }
  return {
    width: `${p}%`,
  }
};

const copy = (
  model,
  source,
  destination,
  droppableSource,
  droppableDestination
) => {
  const sourceClone = Array.from(source.itemIds);
  const destClone = Array.from(destination.items);
  const item = sourceClone[droppableSource.index];

  destClone.splice(droppableDestination.index, 0, {
    ...model[item],
    id: uuidv4(),
  });

  return destClone;
};

const reorder = (list, startIndex, endIndex) => {
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);

  return result;
};

const remove = (list, startIndex) => {
  const result = Array.from(list);
  result.splice(startIndex, 1);
  return result;
};

const nbrOccupiedSlotsInCrate = (items) => {
  return items.reduce((prev, next) => {
    return prev + (next.hp === 8 ? 2 : 1);
  }, 0);
};


/**
 * Component that provides a base layout (aside/main) for the page.
 */
class Layout extends React.PureComponent {

  static get propTypes() {
    return {
      aside: PropTypes.any,
      main: PropTypes.any,
    };
  }

  render() {
    const {
      aside,
      main,
    } = this.props;

    return (
      <div className="layout">

        <aside className="aside">{aside}</aside>

        <section className="main">{main}</section>

      </div>
    );
  }
}


/**
 * Component that renders a product.
 * Used in the aside (e.g backlog of product)
 */
class ProductItem extends React.PureComponent {

  static get propTypes() {
    return {
      id: PropTypes.string.isRequired,
      index: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired,
      price: PropTypes.number.isRequired,
      currency: PropTypes.string.isRequired,
      image: PropTypes.string.isRequired,
      specs: PropTypes.array,
      onClickAddItem: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnClickAddItem = this.handleOnClickAddItem.bind(this);
  }

  handleOnClickAddItem(index, e) {
    if (this.props.onClickAddItem) {
      this.props.onClickAddItem(index);
    }
    e.preventDefault();
  }

  render() {
    const {
      id,
      index,
      name,
      price,
      currency,
      image,
      specs,
    } = this.props;

    const render_specs = (specs && specs.length > 0 && (
      <ul>
        {specs.map((spec, index) =>
          <li key={index}>{spec}</li>
        )}
      </ul>
    ));

    return (
      <section className="productItem">

        <div className="content">
          <h3>{name}</h3>

          <div className="price">{`${currency} ${price}`}</div>

          {render_specs}
        </div>

        <div className="content">

          <button onClick={this.handleOnClickAddItem.bind(this, index)}>
            <img src="/images/shop/icon-add.svg" alt="add" />
          </button>

          <Draggable draggableId={id} index={index}>
            {(provided, snapshot) => (
              <React.Fragment>
                <img
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={productStyle(
                      provided.draggableProps.style,
                      snapshot,
                      true,  // hack: remove weird animation after a drop
                    )}
                    src={image} />

                {/* Allows to simulate a clone */}
                {snapshot.isDragging && (
                  <img className="" src={image} />
                )}
              </React.Fragment>
            )}
          </Draggable>

        </div>

      </section>
    );
  }
}


/**
 * Component that renders a product.
 * Used in the crate
 */
class ProductCartItem extends React.PureComponent {

  static get propTypes() {
    return {
      hovered: PropTypes.bool,
      index: PropTypes.number.isRequired,
      model: PropTypes.object.isRequired,
      data: PropTypes.object,
      onToggleProgress: PropTypes.func,
      onToggleWarning: PropTypes.func,
      onToggleOverlayRemove: PropTypes.func,
      onClickRemoveItem: PropTypes.func,
    };
  }

  static get defaultProps() {
    return {
      hovered: false,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this);
    this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this);
    this.handleOnMouseEnterWarningItem = this.handleOnMouseEnterWarningItem.bind(this);
    this.handleOnMouseLeaveWarningItem = this.handleOnMouseLeaveWarningItem.bind(this);
    this.handleOnMouseEnterRemoveItem = this.handleOnMouseEnterRemoveItem.bind(this);
    this.handleOnMouseLeaveRemoveItem = this.handleOnMouseLeaveRemoveItem.bind(this);
    this.handleOnClickRemoveItem = this.handleOnClickRemoveItem.bind(this);
  }

  handleOnMouseEnterItem(index, e) {
    if (this.props.onToggleProgress) {
      this.props.onToggleProgress(index, true);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveItem(index, e) {
    if (this.props.onToggleProgress) {
      this.props.onToggleProgress(index, false);
    }
    e.preventDefault();
  }

  handleOnMouseEnterWarningItem(index, isWarning, e) {
    if (!isWarning) {
      return;
    }
    if (this.props.onToggleWarning) {
      this.props.onToggleWarning(index, true);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveWarningItem(index, isWarning, e) {
    if (!isWarning) {
      return;
    }
    if (this.props.onToggleWarning) {
      this.props.onToggleWarning(index, false);
    }
    e.preventDefault();
  }

  handleOnMouseEnterRemoveItem(index, e) {
    if (this.props.onToggleOverlayRemove) {
      this.props.onToggleOverlayRemove(index, true);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveRemoveItem(index, e) {
    if (this.props.onToggleOverlayRemove) {
      this.props.onToggleOverlayRemove(index, false);
    }
    e.preventDefault();
  }

  handleOnClickRemoveItem(index, e) {
    if (this.props.onClickRemoveItem) {
      this.props.onClickRemoveItem(index);
    }
  }

  render() {
    const {
      hovered,
      model,
      data,
      index,
    } = this.props;

    let warning;
    if (data && data.warnings) {
      const warningsKeys = Object.keys(data.warnings);
      if (warningsKeys && warningsKeys.length > 0) {
        // we display only the first warning
        warning = data.warnings[warningsKeys[0]];
      }
    }

    let render_progress;
    if (model.showProgress && data) {
      switch(model.type) {
        case 'kasli':
        case 'kasli-backplane':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors available`}</p>
              <p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors available`}</p>
            </div>
          );
          break;

        case 'zotino':
        case 'hd68':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors available`}</p>
            </div>
          );
          break;

        case 'clocker':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors available`}</p>
            </div>
          );
          break;

        default:
          break;
      }
    }

    return (
      <Draggable draggableId={model.id} index={index}>

        {(provided, snapshot) => (
          <div
              ref={provided.innerRef}
              {...provided.draggableProps}
              {...provided.dragHandleProps}
              style={{ ...productStyle(
                provided.draggableProps.style,
                snapshot,
                true,
                hovered ? true : false,
                model.selected ? true : false,
              )}}
              onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
              onMouseLeave={this.handleOnMouseLeaveRemoveItem.bind(this, index)}
              >

            {/* warning container */}
            <div
                style={{'height': '24px'}}
                className="progress-container warning"
                onMouseEnter={this.handleOnMouseEnterWarningItem.bind(this, index, warning)}
                onMouseLeave={this.handleOnMouseLeaveWarningItem.bind(this, index, warning)}>

              {warning && (
                <img className="alert-warning" src={warning ? `/images${warning.icon}` : null} />
              )}

              {warning && model.showWarning &&  (
                <div className="k-popup-warning">
                  <p className="rule warning">
                    <i>{warning.message}</i>
                  </p>
                </div>
              )}
            </div>

            <h6>{model.name}</h6>

            <div
              style={{'height': '250px'}}
              onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
              >

              <img
                  className='item-cart'
                  src={`/images${model.image}`} />
            </div>

            {/* remove container */}
            <div
                style={{'display': model.showOverlayRemove ? 'flex':'none'}}
                className="overlayRemove"
                onClick={this.handleOnClickRemoveItem.bind(this, index)}>

              <img src="/images/shop/icon-remove.svg" alt="rm"/>

              <p>remove</p>
            </div>

            {/* progression container */}
            <div
                style={{'height': '22px'}}
                className="progress-container"
                onMouseEnter={this.handleOnMouseEnterItem.bind(this, index)}
                onMouseLeave={this.handleOnMouseLeaveItem.bind(this, index)}>

              {model.nbrSlotMax > 0 && (
                <div className="nbr-connectors">
                  <div style={{ ...nbrConnectorsStyle(data)}}></div>
                </div>
              )}

              {model.nbrClockMax > 0 && (
                <div className="nbr-clocks">
                  <div style={{ ...nbrClocksStyle(data)}}></div>
                </div>
              )}

              {/* progress info when mouse over */}
              {render_progress}
            </div>

          </div>
        )}

      </Draggable>
    );
  }
}


/**
 * Component that displays a placeholder inside crate.
 * Allows to display how it remains space for the current crate.
 */
class FakePlaceholder extends React.PureComponent {

  static get propTypes() {
    return {
      isDraggingOver: PropTypes.bool,
      nbrSlots: PropTypes.number.isRequired,
      items: PropTypes.array.isRequired,
    };
  }

  render() {
    const {
      isDraggingOver,
      nbrSlots,
      items,
    } = this.props;

    const fakePlaceholder = [];
    const nbrOccupied = nbrOccupiedSlotsInCrate(items);

    for (var i = (nbrSlots - nbrOccupied); i > 0; i--) {
      fakePlaceholder.push(
        <div key={i} style={{
          display: isDraggingOver ? 'none' : 'block',
          border: '1px dashed #ccc',
          width: '45px',
          marginBottom: '5px',
        }}></div>
      );
    }

    return (
      <React.Fragment>
        {fakePlaceholder}
      </React.Fragment>
    );

  }
}


/**
 * Component that displays a list of <ProductCartItem>
 */
class Cart extends React.PureComponent {

  static get propTypes() {
    return {
      nbrSlots: PropTypes.number,
      itemHovered: PropTypes.string,
      data: PropTypes.object.isRequired,
      onToggleProgress: PropTypes.func,
      onToggleWarning: PropTypes.func,
      onToggleOverlayRemove: PropTypes.func,
      onClickRemoveItem: PropTypes.func,
    };
  }

  render() {
    const {
      nbrSlots,
      itemHovered,
      data,
      onToggleProgress,
      onToggleWarning,
      onToggleOverlayRemove,
      onClickRemoveItem,
    } = this.props;

    const products = data.items.map((item, index) => {
      let itemData;
      if (data.itemsData && index in data.itemsData) {
        itemData = data.itemsData[index];
      }
      return (
        <ProductCartItem
            hovered={item.id === itemHovered}
            key={item.id}
            index={index}
            data={itemData}
            onToggleProgress={onToggleProgress}
            onToggleWarning={onToggleWarning}
            onToggleOverlayRemove={onToggleOverlayRemove}
            onClickRemoveItem={onClickRemoveItem}
            model={item}>
        </ProductCartItem>
      );
    });

    return (
      <Droppable droppableId={data.id} direction="horizontal">

        {(provided, snapshot) => (
          <div
              ref={provided.innerRef}
              {...provided.droppableProps}
              style={cartStyle(
                provided.droppableProps.style,
                snapshot,
              )}
              className="items-cart-list">

            {products}

            {provided.placeholder && (
              <div style={{ display: 'none' }}>
                {provided.placeholder}
              </div>
            )}

            <FakePlaceholder
                nbrSlots={nbrSlots}
                items={data.items}
                isDraggingOver={snapshot.isDraggingOver} />
          </div>
        )}

      </Droppable>
    );
  }
}


/**
 * Component that displays crate modes
 */
class CrateMode extends React.PureComponent {

  static get propTypes() {
    return {
      items: PropTypes.array.isRequired,
      mode: PropTypes.string.isRequired,
      onClickMode: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnClickMode = this.handleOnClickMode.bind(this);
  }

  handleOnClickMode(mode, e) {
    if (this.props.onClickMode) {
      this.props.onClickMode(mode);
    }
    e.preventDefault();
  }

  render() {
    const {
      mode,
      items,
    } = this.props;

    return (
      <div className="crate-mode">
        {items.map(item => (
          <a
            key={item.id}
            className={mode == item.id ? 'active' : ''}
            onClick={this.handleOnClickMode.bind(this, item.id)}
            href="#"
            role="button">{item.name}</a>
        ))}
      </div>
    );
  }
}


/**
 * Component that displays the main crate with reminder rules.
 * It includes <Cart> and rules
 */
class Crate extends React.PureComponent {

  static get propTypes() {
    return {
      rules: PropTypes.array,
      cart: PropTypes.element,
    };
  }

  render() {
    const {
      rules,
      cart,
    } = this.props;

    return (
      <div className="crate">

        <div className="crate-products">

          {cart}

          {rules && rules.length > 0 && (
            <div className="crate-info">
              {rules.map((rule, index) => (
                <p key={index} className="rule">
                  <img src={`/images${rule.icon}`} /> <i><strong>{rule.name}:</strong> {rule.message}</i>
                </p>
              ))}
            </div>
          )}
        </div>

      </div>
    );
  }
}


/**
 * Component that renders all things for order.
 * It acts like-a layout, this component do nothing more.
 */
class OrderPanel extends React.PureComponent {

  static get propTypes() {
    return {
      title: PropTypes.string,
      description: PropTypes.string,
      crateMode: PropTypes.element,
      crate: PropTypes.element,
      summaryPrice: PropTypes.element,
      form: PropTypes.element,
    };
  }

  render() {
    const {
      title,
      description,
      crateMode,
      crate,
      summaryPrice,
      form,
    } = this.props;

    return (
      <section className="panel">

        <h2>{title}</h2>

        <div className="control">
          <p className="description">{description}</p>

          {crateMode}
        </div>

        {crate}

        <section className="summary">
          {summaryPrice}

          {form}
        </section>

      </section>
    );
  }
}


/**
 * Components that renders the form to request quote.
 */
class OrderForm extends React.PureComponent {

  static get propTypes() {
    return {
      onClickSubmit: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.state = {note: ''};

    this.handleNoteChange = this.handleNoteChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleNoteChange(event) {
    this.setState({
      note: event.target.value,
    });
  }

  handleSubmit(event) {
    if (this.props.onClickSubmit) {
      this.props.onClickSubmit(this.state.note);
    }
    event.preventDefault();
  }

  render() {
    return (
      <div className="summary-form">

        <form onSubmit={this.handleSubmit}>
          <textarea
              value={this.state.note}
              onChange={this.handleNoteChange}
              rows="5"
              placeholder="Additional notes" />
          <input type="submit" value="Request quote" />
        </form>

      </div>
    );
  }
}


/**
 * Components that displays the list of card that are used in the crate.
 * It is a summary of purchase
 */
class OrderSumary extends React.PureComponent {

  static get propTypes() {
    return {
      modes: PropTypes.array,
      currentMode: PropTypes.string,
      summary: PropTypes.array,
      itemsData: PropTypes.array,
      onDeleteItem: PropTypes.func,
      onDeleteAllItems: PropTypes.func,
      onMouseEnterItem: PropTypes.func,
      onMouseLeaveItem: PropTypes.func,
      onClickSelectItem: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnDeleteItem = this.handleOnDeleteItem.bind(this);
    this.handleOnDeleteAllItems = this.handleOnDeleteAllItems.bind(this);
    this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this);
    this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this);
    this.handleOnClickSelectItem = this.handleOnClickSelectItem.bind(this);
  }

  handleOnDeleteItem(index, e) {
    if (this.props.onDeleteItem) {
      this.props.onDeleteItem(index);
    }
    e.preventDefault();
  }

  handleOnDeleteAllItems(e) {
    if (this.props.onDeleteAllItems) {
      this.props.onDeleteAllItems();
    }
    e.preventDefault();
  }

  handleOnMouseEnterItem(id, e) {
    if (this.props.onMouseEnterItem) {
      this.props.onMouseEnterItem(id);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveItem(e) {
    if (this.props.onMouseLeaveItem) {
      this.props.onMouseLeaveItem();
    }
    e.preventDefault();
  }

  handleOnClickSelectItem(index, e) {
    if (e.target.tagName !== 'IMG') {
      if (this.props.onClickSelectItem) {
        this.props.onClickSelectItem(index);
      }
    }
    return e.preventDefault();
  }

  render() {
    const {
      modes,
      currentMode,
      summary,
      itemsData,
    } = this.props;

    const mode = modes.find(elem => elem.id === currentMode);

    return (
      <div className="summary-price">

        <table>

          <thead>
            <tr>
              <td colSpan="2" className="summary-remove-all">
                <span className="item-card-name">Remove all cards</span>

                <button onClick={this.handleOnDeleteAllItems}>
                  <img src="/images/shop/icon-remove.svg" />
                </button>
              </td>
            </tr>

            {mode && (
              <tr>
                <td className="item-card-name">{mode.name}</td>
                <td className="price">
                  {`${mode.currency} ${mode.price}`}

                  <button style={{'opacity': '0', 'cursor': 'initial'}}>
                    <img src="/images/shop/icon-remove.svg" />
                  </button>
                </td>
              </tr>
            )}
          </thead>

          <tbody>
            {summary.map((item, index) => {
              let alert;
              let warning;

              if (itemsData[index]) {
                alert = itemsData[index];
                const warningsKeys = Object.keys(alert.warnings);
                if (warningsKeys && warningsKeys.length > 0) {
                  warning = alert.warnings[warningsKeys[0]];
                }
              }

              return (
                <tr key={item.id}
                    className={`hoverable ${item.selected ? 'selected' : ''}`}
                    onClick={this.handleOnClickSelectItem.bind(this, index)}
                    onMouseEnter={this.handleOnMouseEnterItem.bind(this, item.id)}
                    onMouseLeave={this.handleOnMouseLeaveItem}>

                  <td className="item-card-name">
                    <div>{item.name}</div>
                  </td>

                  <td className="price">
                    <div>
                      {`${item.currency} ${item.price}`}

                      <button onClick={this.handleOnDeleteItem.bind(this, index)}>
                        <img src="/images/shop/icon-remove.svg" />
                      </button>
                    </div>

                    {warning && (
                      <img
                          style={{'marginLeft': '10px'}}
                          className="alert-warning"
                          src={`/images/${warning.icon}`}
                      />
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>

          <tfoot>
            <tr>
              <td className="item-card-name">Price estimate</td>
              <td className="price">
                {summary.length ? (
                  `${summary[0].currency} ${summary.reduce(
                    (prev, next) => {
                      return prev + next.price;
                    }, 0
                  ) + mode.price}`
                ) : (
                  `${mode.currency} ${mode.price}`
                )}

                <button style={{'opacity': '0', 'cursor': 'initial'}}>
                  <img src="/images/shop/icon-remove.svg" alt="icon remove"/>
                </button>
              </td>
            </tr>
          </tfoot>

        </table>

      </div>
    );
  }
}


/**
 * Component that renders the backlog in the aside
 */
class Backlog extends React.PureComponent {

  static get propTypes() {
    return {
      data: PropTypes.object.isRequired,
      items: PropTypes.object,
      onClickAddItem: PropTypes.func,
    };
  }

  static get defaultProps() {
    return {
      items: {},
    };
  }

  render() {
    const {
      data,
      items,
      onClickAddItem,
    } = this.props;

    const ordered_items = data.itemIds.map(itemId => items[itemId]);
    const products = ordered_items.map((item, index) => {
      return (
        <ProductItem
            key={item.id}
            id={item.id}
            index={index}
            name={item.name}
            price={item.price}
            currency={item.currency}
            image={`/images/${item.image}`}
            specs={item.specs}
            onClickAddItem={onClickAddItem}
        ></ProductItem>
      );
    });

    return (
      <Droppable
          droppableId={data.id}
          isDropDisabled={true}>

        {(provided) => (
          <div
              className="backlog-container"
              ref={provided.innerRef}
              {...provided.droppableProps}>

            {products}

            {provided.placeholder && (
              <div style={{ display: 'none' }}>
                {provided.placeholder}
              </div>
            )}
          </div>
        )}

      </Droppable>
    );
  }
}


/**
 * Component that render the entire shop
 */
class Shop extends React.PureComponent {

  static get propTypes() {
    return {
      data: PropTypes.object.isRequired,
    };
  }

  constructor(props) {
    super(props);
    this.state = this.props.data;
    this.handleCrateModeChange = this.handleCrateModeChange.bind(this);
    this.handleOnDragEnd = this.handleOnDragEnd.bind(this);
    this.handleDeleteItem = this.handleDeleteItem.bind(this);
    this.handleDeleteAllItems = this.handleDeleteAllItems.bind(this);
    this.handleMouseEnterItem = this.handleMouseEnterItem.bind(this);
    this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this);
    this.handleClickAddItem = this.handleClickAddItem.bind(this);
    this.checkAlerts = this.checkAlerts.bind(this);
    this.handleToggleItemProgress = this.handleToggleItemProgress.bind(this);
    this.handleToggleItemWarning = this.handleToggleItemWarning.bind(this);
    this.handleClickSelectItem = this.handleClickSelectItem.bind(this);
    this.handleClickSubmit = this.handleClickSubmit.bind(this);
    this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
  }

  componentDidMount() {
    // index 0 is a Kasli, we place it as a default conf on the crate.
    const source = {
      droppableId: 'backlog',
      index: 0,
    };
    const destination = {
      droppableId: 'cart',
      index: 0,
    };

    this.handleOnDragEnd({
      source,
      destination,
      draggableId: null,
    });
  }

  componentDidUpdate(prevProps, prevState) {
    /**
     * We check alerts (reminder + warning) only when items inside crate or
     * crate mode change.
     *
     * In the function checkAlerts, we DO NOT want to change items as we will
     * trigger again this function (componentDidUpdate) and thus,
     * making an infinite loop.
     */
    if (
      (prevState.columns.cart.items !== this.state.columns.cart.items) ||
      (prevState.currentMode !== this.state.currentMode)
    ) {
      this.checkAlerts(
        prevState.columns.cart.items,
        this.state.columns.cart.items);
    }
  }

  handleCrateModeChange(mode) {
    this.setState({
      currentMode: mode,
    });
  }

  handleDeleteItem(index) {
    const cloned = Array.from(this.state.columns.cart.items);
    cloned.splice(index, 1);
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: cloned,
        },
      },
    });
  }

  handleDeleteAllItems() {
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: [],
        },
      },
    });
  }

  handleMouseEnterItem(id) {
    this.setState({
      ...this.state,
      currentItemHovered: id,
    });
  }

  handleMouseLeaveItem() {
    this.setState({
      ...this.state,
      currentItemHovered: null,
    });
  }

  handleClickAddItem(index) {
    const source = {
      droppableId: 'backlog',
      index: index,
    };
    const destination = {
      droppableId: 'cart',
      index: this.state.columns.cart.items.length,
    };

    this.handleOnDragEnd({
      source,
      destination,
      draggableId: null,
    });
  }

  handleToggleItemProgress(index, show) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, i) => {
            return {
              ...item,
              showProgress: i === index ? show : false,
              showOverlayRemove: false,
              showWarning: false,
            };
          }),
        }
      },
    });
  }

  handleToggleItemWarning(index, show) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, i) => {
            return {
              ...item,
              showWarning: i === index ? show : false,
              showProgress: false,
              showOverlayRemove: false,
            };
          }),
        }
      },
    });
  }

  handleClickSelectItem(index) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, id) => {
            return {...item, selected: id === index ? true : false};
          }),
        }
      },
    });
  }

  handleToggleOverlayRemove(index, show) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, id) => {
            return {
              ...item,
              showOverlayRemove: id === index ? show : false,
              showProgress: false,
              showWarning: false,
            };
          }),
        }
      },
    });
  }

  handleClickSubmit(note) {
    const crate = {
      items: [],
      type: this.state.currentMode,
    };
    const clonedCart = Array.from(this.state.columns.cart.items);
    for (const i in clonedCart) {
      const item = clonedCart[i];
      crate.items.push({
        'name': item.name,
      });
    }

    const a = document.createElement('a');
    const num = (new Date()).getTime();
    const subject = `[Order hardware] - Request Quote`;
    let body = `Hello!\n\nI would like to request a quotation for my below configuration:\n\n${JSON.stringify(crate)}\n\n`;

    if (note) {
      body = `${body}\n\nAdditional note:\n\n${note ? note.trim() : ''}`;
    }

    document.body.appendChild(a);

    a.style = 'display: none';
    a.href = `mailto:sales@m-labs.hk?subject=${subject}&body=${encodeURIComponent(body)}`;
    a.click();
  }

  handleOnDragEnd(result) {
    const {
      source,
      destination,
      draggableId,
    } = result;

    if (!destination) {
      if (source.droppableId === 'cart') {
        this.setState({
          ...this.state,
          columns: {
            ...this.state.columns,
            [source.droppableId]: {
              ...this.state.columns[source.droppableId],
              items: remove(
                this.state.columns[source.droppableId].items,
                source.index,
              ),
            },
          },
        });
      }

      return;
    }

    switch(source.droppableId) {

      case 'backlog':
        this.setState({
          ...this.state,
          columns: {
            ...this.state.columns,
            [destination.droppableId]: {
              ...this.state.columns[destination.droppableId],
              items: copy(
                this.state.items,
                this.state.columns[source.droppableId],
                this.state.columns[destination.droppableId],
                source,
                destination,
              ),
            },
          },
        });
        break;

      case destination.droppableId:
        this.setState({
          ...this.state,
          columns: {
            ...this.state.columns,
            [destination.droppableId]: {
              ...this.state.columns[destination.droppableId],
              items: reorder(
                this.state.columns[destination.droppableId].items,
                source.index,
                destination.index,
              ),
            },
          },
        });
        break;

      default:
        break;
    }
  }

  checkAlerts(prevItems, newItems) {
    console.log('--- START CHECKING CRATE WARNING ---');

    const {
      currentMode,
      crateModeSlots,
      crateRules,
    } = this.state;

    const itemsCloned = Array.from(newItems);
    const itemsData = {};
    const rules = {};


    // check number of slot in crate
    const nbrOccupied = nbrOccupiedSlotsInCrate(newItems);
    if (nbrOccupied > crateModeSlots[currentMode]) {
      rules[crateRules.maxSlot.type] = {...crateRules.maxSlot};
    }


    // check the number of EEM connectors available for all Kasli
    const idxK = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'kasli' || next.type === 'kasli-backplane') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxK.length - 1; i++) {
      let slots;
      let nbUsedSlot = 0;
      let nbrCurrentClock = 0;
      let idx = idxK[i];

      if (i !== idxK.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxK[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      if (i == 0) {
        const slots_need_resource = itemsCloned.slice(0, idx);
        const idx_need = slots_need_resource.findIndex(e => (e.rules && e.rules.resources));

        if (idx_need != -1) {
          if (idx_need in itemsData) {
            if ('warnings' in itemsData[idx_need]) {
              itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
            } else {
              itemsData[idx_need].warnings = {};
              itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
            }
          } else {
            itemsData[idx_need] = {...itemsCloned[idx_need]};
            itemsData[idx_need].warnings = {};
            itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
          }
        }
      }

      nbUsedSlot = slots
        .filter(item => item.type !== 'idc-bnc')
        .reduce((prev, next) => {
          return prev + next.slotOccupied;
        }, 0);
      nbrCurrentClock = slots
        .reduce((prev, next) => {
          return next.type === 'clocker' ? prev + next.clockOccupied : prev;
        }, 0);

      if (idx in itemsData) {
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        if (!('warnings' in itemsData[idx])) {
          itemsData[idx].warnings = {};
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        itemsData[idx].warnings = {};
      }

      if (nbUsedSlot > itemsCloned[idx].nbrSlotMax) {
        rules[itemsCloned[idx].rules.maxSlot.type] = {...itemsCloned[idx].rules.maxSlot};
        itemsData[idx].warnings.maxSlotWarning = {...itemsCloned[idx].rules.maxSlotWarning};
      }

      if (nbrCurrentClock > itemsCloned[idx].nbrClockMax) {
        rules[itemsCloned[idx].rules.maxClock.type] = {...itemsCloned[idx].rules.maxClock};
        itemsData[idx].warnings.maxClockWarning = {...itemsCloned[idx].rules.maxClockWarning};
      }

      if (itemsCloned.length > (idx + 1)) {
        const ddkali = itemsCloned[idx + 1];
        if (ddkali.type === 'kasli' || ddkali.type === 'kasli-backplane') {
          rules[ddkali.rules.follow.type] = {...ddkali.rules.follow};
        }
      }
    }


    // check number of clock connector available
    const idxC = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'kasli' || next.type === 'kasli-backplane' || next.type === 'clocker') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxC.length - 1; i++) {
      let slots;
      let nbrCurrentClock = 0;
      let idx = idxC[i];

      if (i !== idxC.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxC[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      nbrCurrentClock = slots.reduce((prev, next) => {
        return prev + next.clockOccupied;
      }, 0);

      if (idx in itemsData) {
        if (itemsData[idx].nbrCurrentClock) {
          itemsData[idx].nbrCurrentClock += nbrCurrentClock;
        } else {
          itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        itemsData[idx].warnings = {};
      }

      if (nbrCurrentClock > itemsCloned[idx].nbrClockMax) {
        rules[itemsCloned[idx].rules.maxClock.type] = {...itemsCloned[idx].rules.maxClock};
        itemsData[idx].warnings.maxClockWarning = {...itemsCloned[idx].rules.maxClockWarning};
      }
    }


    // check for number of recommanded EEM connectors
    ['novo', 'urukul', 'koster'].map(_type => {
      if (itemsCloned.find(elem => elem.type === _type)) {
        rules[this.state.items[_type].rules.connectors.type] = {...this.state.items[_type].rules.connectors};
      }
      return _type;
    });


    // check if IDC-BNC is correctly positionned (after Zotino or HD68)
    const idxIDCBNC = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'idc-bnc') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (var i = idxIDCBNC.length - 1; i >= 0; i--) {
      const ce = idxIDCBNC[i];
      let shouldWarning = false;

      if (ce == 0) {
        shouldWarning = true;
      } else if (ce >= 1) {
        const pe = idxIDCBNC[i] - 1;
        if (itemsCloned[pe].type !== 'zotino' &&
            itemsCloned[pe].type !== 'hd68' &&
            itemsCloned[pe].type !== 'idc-bnc') {
          shouldWarning = true;
        }
      }

      if (shouldWarning) {
        itemsData[ce] = {...itemsCloned[ce]};
        itemsData[ce].warnings = {};
        itemsData[ce].warnings.wrong = {...itemsCloned[ce].rules.wrong};
      }
    }


    // check number of IDC-BNC adapters for a Zotino and HD68-IDC
    const idxZH = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'zotino' || next.type === 'hd68') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxZH.length - 1; i++) {
      let slots;
      let nbUsedSlot = 0;
      let idx = idxZH[i];

      if (i !== idxZH.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxZH[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      let stopCount = false;
      nbUsedSlot = slots.reduce((prev, next, ci, ca) => {
        if (ci === 0 && next.type === 'idc-bnc') {
          return prev + 1;
        } else if (ca[0].type === 'idc-bnc' && ci > 0 && ca[ci - 1].type === 'idc-bnc') {
          if (next.type !== 'idc-bnc') { stopCount = true; }
          return prev + (next.type === 'idc-bnc' && !stopCount ? 1 : 0);
        }
        return prev;
      }, 0);

      if (idx in itemsData) {
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        if (!('warnings' in itemsData[idx])) {
          itemsData[idx].warnings = {};
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].warnings = {};
      }

      if (nbUsedSlot > 0) {
        rules[itemsCloned[idx].rules.maxSlot.type] = {...itemsCloned[idx].rules.maxSlot};
      }
      if (nbUsedSlot > itemsCloned[idx].nbrSlotMax) {
        itemsData[idx].warnings.maxSlotWarning = {...itemsCloned[idx].rules.maxSlotWarning};
      }

      // check if HD68-IDC has at least 1 IDC-BNC adapter
      if (itemsCloned[idx].type === 'hd68') {
        let shouldWarning = false;

        if (idx < itemsCloned.length - 1) {
          if (itemsCloned[idx + 1].type !== 'idc-bnc') {
            shouldWarning = true;
          }
        } else if (idx === itemsCloned.length - 1) {
          shouldWarning = true;
        }

        if (shouldWarning) {
          if (idx in itemsData) {
            itemsData[idx].warnings.minAdapter = {...itemsCloned[idx].rules.minAdapter};
          } else {
            itemsData[idx] = {...itemsCloned[idx]};
            itemsData[idx].warnings = {};
            itemsData[idx].warnings.minAdapter = {...itemsCloned[idx].rules.minAdapter};
          }
        }
      }
    }


    // update state with rules
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          itemsData: {
            ...itemsData,
          },
        }
      },
      rules: {
        ...rules,
      },
    });
  }

  render() {

    const {
      currentItemHovered,
      currentMode,
      crateModeSlots,
      crateModeItems,
      items,
      columns,
      rules,
    } = this.state;

    return (
      <DragDropContext onDragEnd={this.handleOnDragEnd}>

        <Layout
            className="shop"
            aside={
              <Backlog
                  items={items}
                  data={columns['backlog']}
                  onClickAddItem={this.handleClickAddItem}>
              </Backlog>
            }
            main={(
              <OrderPanel
                  title="Order hardware"
                  description="
                    Choose your crate and drag/add the cards you want
                    to the crate below to see
                    how the combination would look like."
                  crateMode={
                    <CrateMode
                        items={crateModeItems}
                        mode={currentMode}
                        onClickMode={this.handleCrateModeChange}>
                    </CrateMode>}
                  crate={
                    <Crate
                        cart={
                          <Cart
                              nbrSlots={crateModeSlots[currentMode]}
                              data={columns['cart']}
                              itemHovered={currentItemHovered}
                              onToggleProgress={this.handleToggleItemProgress}
                              onToggleWarning={this.handleToggleItemWarning}
                              onToggleOverlayRemove={this.handleToggleOverlayRemove}
                              onClickRemoveItem={this.handleDeleteItem}>
                          </Cart>
                        }
                        rules={Object.values(rules).filter(rule => rule)}>
                    </Crate>
                  }
                  summaryPrice={
                    <OrderSumary
                        currentMode={currentMode}
                        modes={crateModeItems}
                        summary={columns['cart'].items}
                        itemsData={columns.cart.itemsData}
                        onMouseEnterItem={this.handleMouseEnterItem}
                        onMouseLeaveItem={this.handleMouseLeaveItem}
                        onDeleteItem={this.handleDeleteItem}
                        onDeleteAllItems={this.handleDeleteAllItems}
                        onClickSelectItem={this.handleClickSelectItem}>
                    </OrderSumary>
                  }
                  form={
                    <OrderForm
                        onClickSubmit={this.handleClickSubmit}>
                    </OrderForm>
                  }>
              </OrderPanel>
            )}>
        </Layout>

      </DragDropContext>
    );
  }
}


ReactDOM.render(
  <Shop data={data} />,
  document.querySelector('#root-shop'),
);
