/* Polyfills for IE */
import 'react-app-polyfill/ie11';
import 'core-js/features/array/find';
import 'core-js/features/array/includes';
import 'core-js/features/number/is-nan';

/* App starts here */
import React from "react";
import { withRouter, NavLink, Route, Switch } from "react-router-dom";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";

import "bootstrap/dist/css/bootstrap.min.css";

/* Custom components */

import './modest.css';
import "./App.css";

class Header extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
  }

  render() {
    return (
      <div className="Header">
        <Logo/>
      </div>
    );
  }
}

class Logo extends React.Component {
  render() {
    return <NavLink to="/"><div className="Logo"></div></NavLink>
  }
}

class Footer extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="Footer">
        <div className="Copyright">Copyright 2020 James Ketrenos</div>
      </div>
    );
  }
}


function noChange() {};
  
class Identities extends React.Component {
  constructor(props) {
    super(props);
  }
  
  componentDidMount() {
    let params = window.location.search ? window.location.search.replace(/^\?/, "").split("&") : [],
      face, identity;

    for (let i in params) {
      let parts = params[i].split("=");
      switch (parts[0]) {
      case "face":
        face = parseInt(parts[1]);
        if (face == parts[1]) {
          loadFace(face);
        }
        break;
      case "identity":
        identity = parseInt(parts[1]);
        if (identity == parts[1]) {
          getIdentity(identity);
        }
        break;
      }
    }
    if (!face && !identity) {
      loadFace();
    }
  }

  render() {
    return (
      <div className="Identities">
        <div>
          <div id="face-editor" className="block"></div>
          <div id="best-match" className="block"></div>
          <div id="new-identity" className="block"></div>
          <div id="photo-explorer" className="block"></div>
        </div>
        <div id="identities" className="block"></div>
      </div>
    )
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
  }
    
  componentDidMount() {
    const location = this.props.history.location.pathname;
    console.log(`App.mounted at ${location}`);
  }

  render() {
    const tmp = document.querySelector("base");
    let base;
    if (tmp) {
      base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
    } else {
      base = "/";
    }

    console.log("App: " + base);
    return (
      <div className="App" ref={ ref => (this.app = ref) }>
        <Header/>
        <div className="Body">
          <div className="Main">
            <Switch>
              <Route path="/identities" render={ (props) => <Identities {...props}/> }/>
              <Route path={ base + "identities" } render={ (props) => <Identities {...props}/> }/>
              <Route path="/faces" render={ props => <div {...props}/> }/>
              <Route path="/photos" render={ props => <div {...props}/> }/>
              <Route>{base + "identities"}</Route>
            </Switch>
          </div>
        </div>
        <Footer/>
      </div>
    );
  }
}

export default withRouter(App);

/* 

1. Query server for a face with no bound identity. 
2. Query for faces related to that face
3. Put a UI that let's the user pick from existing identities.
4. Select / Deselect all faces that match
5. "Match" to bind the faces to the identity
6. Create new Identity 

*/

function createFace(faceId, photoId, selectable) {
  var div = document.createElement("div");
  div.classList.add("face");
  div.setAttribute("photo-id", photoId);
  div.setAttribute("face-id", faceId);
  div.style.backgroundImage = "url(face-data/" + (faceId % 100) + "/" + faceId + "-original.png)";
  div.addEventListener("click", (event) => {
    if (event.shiftKey) { /* identities */
      let faceId = parseInt(event.currentTarget.getAttribute("face-id"));
      if (faceId) {
        if (event.ctrlKey) {
          window.open("identities?face=" + faceId, "faceId-" + faceId);
        } else {
          window.location.href = "identities?face=" + faceId;
        }
      } else {
        alert("No face id mapped to face.");
      }
    } else if (!selectable || event.ctrlKey) { /* face-explorer */
      let photoId = parseInt(event.currentTarget.getAttribute("photo-id"));
      if (photoId) {
        window.open("face-explorer.html?" + photoId, "photo-" + photoId);
      } else {
        alert("No photo id mapped to face.");
      }
    } else {
      if (event.currentTarget.hasAttribute("disabled")) {
        event.currentTarget.removeAttribute("disabled");
      } else {
        event.currentTarget.setAttribute("disabled", "");
      }
    }
  });
  return div;
}

function loadPhoto(photo) {
  const tmp = document.querySelector("base");
  let base;
  if (tmp) {
      base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
  } else {
      base = "/";
  }

  const photoExplorerBlock = document.getElementById("photo-explorer");
  photoExplorerBlock.innerHTML = "<div id='photo-info'></div>";

  const url = base + photo.path + "thumbs/scaled/" + photo.filename,
    taken = new Date(photo.taken),
    xml = new XMLHttpRequest(),
    loading = document.getElementById("loading");
    
  if (loading) {
    loading.textContent = "0%";
  }

  const info = photoExplorerBlock.querySelector("div");
  info.textContent = photo.path + photo.filename;
  const bar = document.getElementById("bar");
  if (bar) {
    bar.parentElement.removeChild(bar);
  }
  xml.onprogress = function (event) {
    var alpha = 0;
    if (event.total) {
      alpha = event.loaded / event.total;
      if (loading) {
        loading.textContent = Math.ceil(100 * alpha) + "%";
      }
    } else {
      if (loading) {
        loading.textContent = "0%";
      }
    }
  };

  xml.onload = function(event) {
    photoExplorerBlock.style.backgroundImage = "url(" + encodeURI(url).replace(/\(/g, '%28').replace(/\)/g, '%29') + ")";
    /* Give the DOM time to update before fitting boxes... */
    setTimeout(() => {
      makeFaceBoxes(photo, photoExplorerBlock);
    }, 250);
  }

  xml.open("GET", url, true);
  xml.send();
}

function makeFaceBoxes(photo, photoExplorer) {
  if (!photo.faces || !photo.faces.length) {
    return;
  }
  
  let scale;

  /* If photo aspect ratio is higher than viewport aspect ratio, width will be scaled */
  if (photo.width / photo.height > photoExplorer.offsetWidth / photoExplorer.offsetHeight) {
    scale = photoExplorer.offsetWidth / photo.width;
  } else {
    scale = photoExplorer.offsetHeight / photo.height;
  }
  
  photo.faces.forEach((face) => {
    const box = document.createElement("div");
    box.classList.add("face-box");
    photoExplorer.appendChild(box);
    box.style.left = (photo.width * face.left * scale) + "px";
    box.style.top = (photo.height * face.top * scale) + "px";
    box.style.width = (photo.width * (face.right - face.left) * scale)  + "px";
    box.style.height = (photo.height * (face.bottom - face.top) * scale)  + "px";
    box.addEventListener("click", () => {
      if (event.ctrlKey) { /* face-explorer */
        window.open("face-explorer.html?" + photo.id, "photo-" + photo.id);
      }
      if (event.shiftKey) {
        window.open("identities?face=" + face.id, "faceId-" + face.id);
      } else {
        const identitiesBlock = document.getElementById("identities");
        identitiesBlock.innerHTML = "";
        loadFace(face.id);
      }
    });
  });
}

function loadFace(id) {
  const faceEditorBlock = document.getElementById("face-editor");
  faceEditorBlock.innerHTML = "";

  const photoExplorerBlock = document.getElementById("photo-explorer");
  photoExplorerBlock.innerHTML = "";

  window.fetch("api/v1/faces" + (id ? "/" + id : "")).then(res => res.json()).then((faces) => {
    if (faces.length == 0) {
      getIdentities();
      return;
    }

    const newIdentity = document.getElementById("new-identity");
    newIdentity.innerHTML = "";
    newIdentity.appendChild(createNewIdentityEditor());

    const face = faces[0];

    window.fetch("api/v1/photos/random/" + face.photoId).then(res => res.json()).then((photo) => {
      loadPhoto(photo);
    });

    getIdentities(face.id);

    const editor = document.createElement("div");
    editor.classList.add("editor");
    editor.appendChild(createFace(face.id, face.photoId));
    let row = document.createElement("div"),
      label = document.createElement("div"),
      a = document.createElement("a");
    row.classList.add("editor-row");
    label.textContent = "View photo: ";
    row.appendChild(label);
    a.setAttribute("target", "photo-" + face.photoId);
    a.href = "face-explorer.html?" + face.photoId;
    a.textContent = "photo " + face.photoId;
    row.appendChild(a);
    editor.appendChild(row);
    row = document.createElement("div");
    row.classList.add("editor-row");
    let message = "Unassigned related faces: ";
    if (face.relatedFaces.length > 0) {
      message += face.relatedFaces.length + " related (tap to deselect)";
    } else {
      message += "No matches found.";
    }
    row.textContent = message;
    editor.appendChild(row);

    row = document.createElement("div");
    row.classList.add("editor-row");
    row.classList.add("related-faces");

    editor.appendChild(row);

    let button = document.createElement("button");
    button.textContent = "show more";
    button.addEventListener("click", (event) => {
      showMore(row, face.relatedFaces);
    });

    editor.appendChild(button);

    showMore(row, face.relatedFaces);

    faceEditorBlock.appendChild(editor);
  });
} 

function showMore(row, faces) {
  let index = row.querySelectorAll(".face").length,
    max = Math.min(faces.length - index, 9);
  let more = index + max < faces.length;
  for (let i = index; i < index + max; i++) {
    let relation = faces[i];
    const distance = document.createElement("div"),
      facePhoto = createFace(relation.faceId, relation.photoId, true);
    distance.classList.add("distance");
    distance.textContent = relation.distance.toFixed(2);
    facePhoto.appendChild(distance);
    row.appendChild(facePhoto);
  }
  if (!more) {
    row.parentElement.querySelector("button").style.display = "none";
  }
}


function getIdentity(identityId) {
  const identitiesBlock = document.getElementById("identities");
  identitiesBlock.innerHTML = "";

  /* For each identity, create the identityBlock */
  window.fetch("api/v1/identities/" + identityId).then(res => res.json()).then((identities) => {
    identities.forEach((identity) => {
      const block = createIdentityBlock(identity, true);

      /* If face is being edited, show the 'use this identity' button */
      if (!document.body.querySelector("#face-editor .face")) {
        const button = createUseThisIdentityButton(identity.id);
        block.insertBefore(button, block.firstChild);
      }

      identitiesBlock.appendChild(block);
    })
  });
}

function getIdentities(faceId) {
  const identitiesBlock = document.getElementById("identities");
  identitiesBlock.innerHTML = "";

  const search = faceId ? "?withScore=" + faceId : "";
  window.fetch("api/v1/identities" + search).then(res => res.json()).then((identities) => {
    identities.sort((a, b) => {
      if (a.lastName == b.lastName) {
        return a.firstName.localeCompare(b.firstName);
      }
      return a.lastName.localeCompare(b.lastName);
    });

    let minDistance = {
      distance: 1
    };

    identities.forEach((identity) => {
      identity.relatedFaces.forEach((face) => {
        if (face.distance < minDistance.distance) {
          minDistance.distance = face.distance;
          minDistance.photoId = face.photoId;
          minDistance.faceId = face.faceId;
          minDistance.identity = identity;
        }
      });

      const block = createIdentityBlock(identity);

      /* If face is being edited, show the 'use this identity'  */
      if (document.body.querySelector("#face-editor .face")) {
        const button = createUseThisIdentityButton(identity.id);
        block.insertBefore(button, block.firstChild);
      }

      identitiesBlock.appendChild(block);
    });

    /* If no face is being edited, don't show the 'best match' */
    if (!document.body.querySelector("#face-editor .face")) {
      return;
    }

    const bestMatch = document.getElementById("best-match");
    const buttonBlock = createActionButtons();
    if (minDistance.distance != 1) {
      bestMatch.innerHTML = "";
      bestMatch.appendChild(document.createElement("div"));
      bestMatch.firstChild.textContent = `Best match: ${minDistance.identity.name}`;
      const distance = document.createElement("div"),
        facePhoto = createFace(minDistance.faceId, minDistance.photoId);
      distance.classList.add("distance");
      distance.textContent = minDistance.distance.toFixed(2);
      facePhoto.appendChild(distance);
      bestMatch.appendChild(facePhoto);
      /* If no face is being edited, don't show the 'use this identity' button */
      if (document.body.querySelector("#face-editor .face")) {
        buttonBlock.insertBefore(createUseThisIdentityButton(minDistance.identity.id), buttonBlock.firstChild);
      }
    } else {
      bestMatch.innerHTML = "No best guess for match.";
    }
    bestMatch.insertBefore(buttonBlock, bestMatch.firstChild);
  });
}


function createUseThisIdentityButton(identityId) {
  const button = document.createElement("button");
  button.textContent = "Use this identity";
  button.addEventListener("click", (event) => {
    const identitiesBlock = document.getElementById("identities");
    identitiesBlock.innerHTML = "";
    const object = {
      faces: []
    };
    Array.prototype.forEach.call(document.body.querySelectorAll("#face-editor .face"), (face) => {
      if (!face.hasAttribute("disabled")) {
        object.faces.push(face.getAttribute("face-id"));
      }
    });
    window.fetch("api/v1/identities/faces/add/" + identityId, {
      method: "PUT",
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(object)
    }).then(res => res.json()).then((identities) => {
      console.log("Updated identity: ", identities[0]);
      const identitiesBlock = document.getElementById("identities");
      identitiesBlock.innerHTML = "";
      loadFace();
    });
  });
  return button;
}


function createActionButtons() {
  const buttonBlock = document.createElement("div"),
    face = document.body.querySelector("#face-editor .face"),
    faceId = parseInt(face.getAttribute("face-id")),
    photoId =  parseInt(face.getAttribute("photo-id"));

  let button = document.createElement("button");

  button = document.createElement("button");
  button.textContent = "Load random face";
  button.addEventListener("click", (event) => {
    const identitiesBlock = document.getElementById("identities");
    identitiesBlock.innerHTML = "";
    loadFace();
  });
  buttonBlock.appendChild(button);

  button = document.createElement("button");
  button.textContent = "Not a face";
  button.addEventListener("click", (event) => {
  window.fetch("api/v1/faces/" + faceId, {
      method: "DELETE",
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    }).then(res => res.json()).then(() => {
      const identitiesBlock = document.getElementById("identities");
      identitiesBlock.innerHTML = "";
      loadFace();
    });
  });
  buttonBlock.appendChild(button);

  button = document.createElement("button");
  button.textContent = "Delete photo";
  button.addEventListener("click", (event) => {
  window.fetch("api/v1/photos/" + photoId, {
      method: "DELETE",
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    }).then(res => res.json()).then(() => {
      const identitiesBlock = document.getElementById("identities");
      identitiesBlock.innerHTML = "";
      loadFace();
    });
  });
  buttonBlock.appendChild(button);

  return buttonBlock;
}

function createIdentityBlock(identity, nolimit) {
  const block = document.createElement("div");
  
  block.classList.add("block");
  let div = document.createElement("div"),
    a = document.createElement("a");
  div.appendChild(a);
  a.target = "identities?identity-" + identity.id;
  a.href = "identities?identity=" + identity.id;
  if (identity.lastName && identity.firstName) {
    a.textContent = `${identity.lastName}, ${identity.firstName}`;
  } else if (identity.lastName) {
    a.textContent = identity.lastName;
  } else {
    a.textContent = identity.name;
  }
  
  block.appendChild(div);

  div = document.createElement("div");
  div.textContent = "Related faces " + identity.relatedFaces.length;
  block.appendChild(div);

  identity.relatedFaces.sort((a, b) => {
    return a.distance - b.distance;
  });
  
  div = document.createElement("div");
  div.classList.add("face-block");
  if (nolimit) {
    div.classList.add("face-block-nolimit");
  }

  for (let i = 0; i < identity.relatedFaces.length; i++) {
    if (!nolimit && i >= 4) {
      break;
    }
    const target = identity.relatedFaces[i];
    const facePhoto = createFace(target.faceId, target.photoId),
      distance = document.createElement("div");

    distance.classList.add("distance");
    distance.textContent = target.distance.toFixed(2);
    facePhoto.appendChild(distance);
    div.appendChild(facePhoto);
  }

  block.appendChild(div);

  return block;
}

function createNewIdentityEditor() {
  const block = document.createElement("div");
    block.classList.add("block");
  const editor = document.createElement("div");
      editor.classList.add("editor");
    
  const button = document.createElement("button");
  button.textContent = "New Identity";
  editor.appendChild(button);

  [ "lastName", "firstName", "middleName", "name" ].forEach((key) => {
    const row = document.createElement("div"),
      left = document.createElement("div"),
      right = document.createElement("input");
    row.classList.add("editor-row");
    left.classList.add("key");
    left.textContent = key;
    row.appendChild(left);
    row.appendChild(right);
    editor.appendChild(row);
  });

  button.addEventListener("click", (event) => {
    const rows = event.currentTarget.parentElement.querySelectorAll(".editor-row"),
      object = {};
    Array.prototype.forEach.call(rows, (row) => {
      object[row.firstChild.textContent] = row.lastChild.value;
    });
    object.faces = [];
    Array.prototype.forEach.call(document.body.querySelectorAll("#face-editor .face"), (face) => {
      if (!face.hasAttribute("disabled")) {
        object.faces.push(face.getAttribute("face-id"));
      }
    });
      const identitiesBlock = document.getElementById("identities");
      identitiesBlock.innerHTML = "";
      window.fetch("api/v1/identities", {
      method: "POST",
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(object)
    }).then(res => res.json()).then((identities) => {
      console.log("New identity: ", identities[0]);
      let inputs = editor.querySelectorAll("input");
      Array.prototype.forEach.call(inputs, (el) => {
        if (el.value) {
          el.value = "";
        }
      });
      const face = document.body.querySelector("#face-editor .face"),
        faceId = parseInt(face.getAttribute("face-id"));
      const identitiesBlock = document.getElementById("identities");
      identitiesBlock.innerHTML = "";
      loadFace(faceId);
      getIdentities(faceId);
    });
  });
  block.appendChild(editor);

  return block;
}
