"use strict";

const express = require("express"),
  fs = require("fs"),
  config = require("config"),
  moment = require("moment-holiday"),
  crypto = require("crypto"),
  util = require("util"),
  Promise = require("bluebird");

require("../lib/pascha.js")(moment);

const execFile = util.promisify(require("child_process").execFile);

let photoDB;

require("../db/photos").then(function(db) {
  photoDB = db;
});

const router = express.Router();
const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/";

const unlink = function (_path) {
  if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
    _path = _path.substring(picturesPath.length);
  }

  let path = picturesPath + _path;

  return new Promise(function (resolve, reject) {
    fs.unlink(path, function (error, stats) {
      if (error) {
        return reject(error);
      }
      return resolve(stats);
    });
  });
}

const rename = function (_src, _dst) {
  if (_src.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
    _src = _src.substring(picturesPath.length);
  }
  if (_dst.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
    _dst = _dst.substring(picturesPath.length);
  }

  let src = picturesPath + _src,
    dst = picturesPath + _dst;

  return new Promise(function (resolve, reject) {
    fs.rename(src, dst, function (error, stats) {
      if (error) {
        return reject(error);
      }
      return resolve(stats);
    });
  });
}


const computeHash = function(_filepath) {
  if (_filepath.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
    _filepath = _filepath.substring(picturesPath.length);
  }

  let filepath = picturesPath + _filepath;
  return new Promise(function(resolve, reject) {
    let input = fs.createReadStream(filepath),
      hash = crypto.createHash("sha256");
    if (!input) {
      return reject();
    }

    input.on("readable", function() {
      const data = input.read();
      if (data) {
        hash.update(data);
      } else {
        input.close();
        resolve(hash.digest("hex"));
        hash = null;
        input = null;
      }
    });
  });
};

const stat = function (_path) {
  if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
    _path = _path.substring(picturesPath.length);
  }

  let path = picturesPath + _path;

  return new Promise(function (resolve, reject) {
    fs.stat(path, function (error, stats) {
      if (error) {
        return reject(error);
      }
      return resolve(stats);
    });
  });
}

const sharp = require("sharp");

const inProcess = [];


router.put("/:id", function(req, res/*, next*/) {
  if (!req.user.maintainer) {
    return res.status(401).send("Unauthorized to modify photos.");
  }

  const replacements = {
    id: req.params.id
  };

  if (inProcess.indexOf(req.params.id) != -1) {
    console.log("Request to modify asset currently in modification: " + req.params.id);
    return res.status(409).send("Asset " + req.params.id + " is already being processed. Please try again.");
  }

  inProcess.push(req.params.id);

  console.log("PUT /" + replacements.id, req.query);
  switch (req.query.a) {
  case "undelete":
    console.log("Undeleting " + req.params.id);
    return photoDB.sequelize.query("UPDATE photos SET deleted=0,updated=CURRENT_TIMESTAMP WHERE id=:id", {
      replacements: replacements
    }).then(function() {
      return res.status(200).send(req.params.id + " updated.");
    }).catch(function(error) {
      console.log(error);
      return res.status(500).send(error);
    }).then(function() {
      inProcess.splice(inProcess.indexOf(req.params.id), 1);
    });

  case "rename":
    return getPhoto(req.params.id).then(function(asset) {
      if (!asset) {
        return res.status(404).send(req.params.id + " not found.");
      }

      let src = asset.filename;

      asset.filename = asset.filename.replace(/(\.[^.]*)$/, "-" + asset.hash.substring(0, 8) + "$1");
      return rename(picturesPath + asset.path + src, picturesPath + asset.path + asset.filename).then(function() {
        return rename(picturesPath + asset.path + "thumbs/" + src, picturesPath + asset.path + "thumbs/" + asset.filename);
      }).then(function() {
          return rename(picturesPath + asset.path + "thumbs/scaled/" + src, picturesPath + asset.path + "thumbs/scaled/" + asset.filename);
      }).then(function() {
        return photoDB.sequelize.query("UPDATE photos SET filename=:filename WHERE id=:id", {
          replacements: asset
        }).then(function() {
          return res.status(200).send(asset);
        });
      });
    }).catch(function(error) {
      console.log(error);
      return res.status(500).send(error);
    }).then(function() {
      inProcess.splice(inProcess.indexOf(req.params.id), 1);
    });

  case "rotate":
    let direction = req.query.direction || "right";
    if (direction == "right") {
      direction = 90;
    } else {
      direction = -90;
    }
    return getPhoto(req.params.id).then(function(asset) {
      if (!asset) {
        return res.status(404).send(req.params.id + " not found.");
      }
      
      let original = picturesPath + asset.path + asset.filename,
        target = picturesPath + asset.path + ".tmp." + asset.filename,
        thumb = picturesPath + asset.path + "thumbs/" + asset.filename,
        scaled = picturesPath + asset.path + "thumbs/scaled/" + asset.filename;

      let tmp = asset.width;
      asset.width = asset.height;
      asset.height = tmp;

      asset.image = sharp(original);
      return asset.image.rotate(direction).withMetadata().toFile(target).then(function() {
        let stamp = moment(new Date(asset.modified)).format("YYYYMMDDhhmm.ss");
        console.log("Restamping " + target + " to " + stamp);
        /* Re-stamp the file's ctime with the original ctime */
        return execFile("touch", [
          "-t",
          stamp,
          target
        ]);
      }).then(function() {
        return asset.image.rotate(direction).resize(256, 256).withMetadata().toFile(thumb);
      }).then(function() {
        return asset.image.resize(Math.min(1024, asset.width)).withMetadata().toFile(scaled);
      }).then(function() {
        return stat(target).then(function(stats) {
          if (!stats) {
            throw "Unable to find original file after attempting to rotate!";
          }
          asset.size = stats.size;
          asset.modified = stats.mtime;
          return unlink(original).then(function() {
            return rename(target, original);
          }).then(function() {
            return photoDB.sequelize.query("UPDATE photos SET " +
              "modified=:modified,width=:width,height=:height,size=:size,updated=CURRENT_TIMESTAMP,scanned=CURRENT_TIMESTAMP " +
              "WHERE id=:id", {
                replacements: asset
            });
          });
        });
      }).then(function() {
        sharp.cache(false);
        sharp.cache(true);
        res.status(200).send(asset);

        return computeHash(asset.filepath).then(function(hash) {
          asset.hash = hash;
          return asset;
        }).then(function(asset) {
          return photoDB.sequelize.query("SELECT photos.id,photohashes.*,photos.filename,albums.path FROM photohashes " +
            "LEFT JOIN photos ON (photos.id=photohashes.photoId) " +
            "LEFT JOIN albums ON (albums.id=photos.albumId) " +
            "WHERE hash=:hash OR photoId=:id", {
            replacements: asset,
            type: photoDB.sequelize.QueryTypes.SELECT
          }).then(function(results) {
            let query;
    
            if (results.length == 0) {
              query = "INSERT INTO photohashes (hash,photoId) VALUES(:hash,:id)";
              console.warn("HASH being updated and photoId " + asset.id + " *should* already exist, but it doesn't.");
            } else if (results.length > 1) {
              /* This image is now a duplicate! */
              for (var i = 0; i < results.length; i++) {
                if (results[i].id != asset.id) {
                  console.log("Duplicate asset: " + 
                    "'" + asset.filepath + "' is a copy of " +
                    "'" + results[i].path + results[i].filename + "'");
                  asset.duplicate = results[i].id;
                  break;
                }
              }

              query = "UPDATE photos SET duplicate=:duplicate WHERE id=:id; " +
                "DELETE FROM photohashes WHERE photoId=:id";

              console.log("Updating photo " + asset.id + " as duplicate of " + asset.duplicate);
            } else if (results[0].hash != asset.hash) {
              query = "UPDATE photohashes SET hash=:hash WHERE photoId=:id; UPDATE photos SET duplicate=0 WHERE id=:id";

              console.log("Updating photohash for " + asset.id + " to " + asset.hash + ", and clearing duplicate field.");
            } else {
              console.log("Unexpected!", asset, results[0]);
              return;
            }

            return photoDB.sequelize.query(query, {
              replacements: asset,
            }).then(function() {
              return asset;
            });
          });
        });
      });
    }).catch(function(error) {
      console.log(error);
      return res.status(500).send(error);
    }).then(function() {
      inProcess.splice(inProcess.indexOf(req.params.id), 1);
    });
  }

  return res.status(400).send("Invalid request");
});

const getPhoto = function(id) {
  return photoDB.sequelize.query("SELECT " +
    "photos.*,albums.path AS path,photohashes.hash,modified,(albums.path || photos.filename) AS filepath FROM photos " +
    "LEFT JOIN albums ON albums.id=photos.albumId " +
    "LEFT JOIN photohashes ON photohashes.photoId=photos.id " +
    "WHERE photos.id=:id", {
    replacements: {
      id: id
    },
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(photos) {
    if (photos.length == 0) {
      return null;
    }

    if (!photos[0].duplicate) {
      return photos[0];
    }

    return photoDB.sequelize.query("SELECT hash FROM photohashes WHERE photoId=:duplicate", {
      replacements: photos[0],
      type: photoDB.Sequelize.QueryTypes.SELECT,
      raw: true
    }).then(function(results) {
      if (results.length != 0) {
        photos[0].hash = results[0].hash;
      }
      return photos[0];
    });
  });
}

const tasks = [];
const Task = function() {
  return new Promise(function(resolve, reject) {
    crypto.randomBytes(16, function(error, buffer) {
      if (error) {
        return reject(error);
      }

      let now = Date.now(), task;

      task = {
        started: now,
        lastUpdate: now,
        eta: now,
        processed: 0,
        remaining: 0,
        token: buffer.toString('hex'),
      };

      tasks.push(task);
      return resolve(task);
    });
  });
};

const removeTask = function(task) {
  let i = tasks.indexOf(task);
  if (i != -1) {
    tasks.splice(i, 1);
  }
}

router.get("/status/:token", function(req, res) {
  if (!req.params.token) {
    return res.status(400).send("Usage /status/:token");
  }

  for (var i = 0; i < tasks.length; i++) {
    if (tasks[i].token == req.params.token) {
      return res.status(200).send(tasks[i]);
    }
  }
  
  return res.status(404).send("Task " + req.params.token + " not found.");
});

/**
 * 1. Look if there are duplicates.
 * 2. Update all other duplicates to be duplicates of the first image that remains
 * 3. If no duplicates, DELETE the entry from photohashes
 * 4. If there are duplicates, update the HASH entry to point to the first image that remains
 * 5. Delete the entry from photos
 * 6. Delete the scaled, thumb, and original from disk
 */
const deletePhoto = function(photo) {
  return photoDB.sequelize.transaction(function(transaction) {
    return photoDB.sequelize.query("SELECT id FROM photos WHERE duplicate=:id", {
      replacements: photo,
      type: photoDB.Sequelize.QueryTypes.SELECT,
      raw: true
    }).then(function(duplicates) {
      if (!duplicates.length) {
        return null;
      }

      let first = duplicates.shift();
      let needsUpdate = [];
      duplicates.forEach(function(duplicate) {
        needsUpdate.push(duplicate.id);
      });
    
      if (!needsUpdate.length) {
        return first;
      }

      // 2. Update all other duplicates to be duplicates of the first image that remains
      console.log("Updating " + needsUpdate + " to point to " + first.id);
      return photoDB.sequelize.query(
        "UPDATE photos SET duplicate=:first WHERE id IN (:needsUpdate)", {
        replacements: {
          first: first.id,
          needsUpdate: needsUpdate
        },
        transaction: transaction
      }).then(function() {
        return first;
      });
    }).then(function(first) {
      if (!first) {
        console.log("Deleting "+ photo.id + " from photohash.");
        // 3. If no duplicates, DELETE the entry from photohashes
        return photoDB.sequelize.query(
          "DELETE FROM photohashes WHERE photoId=:id", {
          replacements: photo,
          transaction: transaction
        });
      }
      console.log("Updating photohash for " + photo.id + " to point to " + first.id);
      // 4. If there are duplicates, update the HASH entry to point to the first image that remains
      return photoDB.sequelize.query(
        "UPDATE photohashes SET photoId=:first WHERE photoId=:photo", {
        replacements: {
          first: first.id,
          photo: photo.id
        },
        transaction: transaction
      });
    }).then(function() {
      console.log("Deleting " + photo.path + photo.filename + " from DB.");
      // 5. Delete the entry from photos
      return photoDB.sequelize.query("DELETE FROM photos WHERE id=:id", {
        replacements: photo,
        transaction: transaction
      });
    }).then(function() {
      // 6. Delete the scaled, thumb, and original from disk
      console.log("Deleting " + photo.path + photo.filename + " from disk.");
      return unlink(photo.path + "thumbs/scaled/" + photo.filename).catch(function() {}).then(function() {
        return unlink(photo.path + "thumbs/" + photo.filename).catch(function() {}).then(function() {
          return unlink(photo.path + photo.filename).catch(function(error) {
            console.log("Error removing file: " + error);
          });
        });
      });
    });
  });
}

router.delete("/:id?", function(req, res/*, next*/) {
  if (!req.user.maintainer) {
    return res.status(401).send("Unauthorized to delete photos.");
  }

  const replacements = {
    id: req.params.id || "*"
  };

  if (!req.params.id && !req.query.permanent) {
    return res.status(400).send("Trash can only be emptied if permanent.");
  }

  let where = "";
  if (req.params.id) {
    where = "photos.id=:id";
    if (req.query.permanent) {
      where +=  " AND photos.deleted=1";
    }
  } else {
    where = "photos.deleted=1";
  }

  console.log("DELETE /" + replacements.id, req.query);

  let sent = false;

  return photoDB.sequelize.query("SELECT " +
    "photos.*,albums.path AS path,photohashes.hash,(albums.path || photos.filename) AS filepath FROM photos " +
    "LEFT JOIN albums ON albums.id=photos.albumId " +
    "LEFT JOIN photohashes ON photohashes.photoId=photos.id " +
    "WHERE " + where, {
    replacements: replacements,
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(photos) {
    if (photos.length == 0) {
      sent = true;
      return res.status(404).send("Unable to find photo " + replacements.id);
    }

    if (!req.query.permanent) {
      return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
        replacements: replacements
      }).then(function() {
        sent = true;
        return res.status(200).send({
          id: req.params.id,
          deleted: true,
          permanent: (req.query && req.query.permanent) || false
        });
      });
    }
    
    /**
     * Delete the asset from disk and the DB
     */
    return Task().then(function(task) {
      task.remaining = photos.length;
      task.eta = -1;

      sent = true;
      res.status(200).send({
        id: req.params.id,
        task: task,
        permanent: (req.query && req.query.permanent) || false
      });

      return Promise.mapSeries(photos, function(photo) {
        let lastStamp = Date.now();
        return deletePhoto(photo).then(function() {
          let now = Date.now(), elapsed = now - lastStamp;
          lastStamp= now;
          task.processed++;
          task.remaining--;
          task.lastUpdate = Date.now();
          task.eta = elapsed * task.remaining;
        })
      }).then(function() {
        console.log("Processing task " + task.token + " finished: " + (task.lastUpdate - task.started));
        removeTask(task);
      }).catch(function(error) {
        console.log("Processing task " + task.token + " failed: " + error);
        removeTask(task);
      });
    });
  }).catch(function(error) {
    console.log(error);
    if (!sent) {
      sent = true;
      res.status(500).send("Unable to delete photo " + req.params.id + ": " + error);
    }
  });
});

router.get("/holiday/:holiday", function(req, res/*, next*/) {
  let startYear = 1990,
    endYear = moment().year(),
    dayIsHoliday = "",
    holidayName,
    date = undefined;

  /* Find the holiday in the list of holidays */
  let lookup = moment().holidays([req.params.holiday]);
  if (!lookup) {
    date = req.params.holiday.match(/^((\d{4})-)?(\d{2})-(\d{2})$/);
    if (!date) {
      return res.status(404).send(req.params.holiday + " holiday not found.");
    }
    date = {
      year: date[1],
      month: date[3],
      day: date[4]
    };
    if (date.year) {
      startYear = date.year;
      endYear = date.year;
    }
  }

  holidayName = date ? req.params.holiday : Object.getOwnPropertyNames(lookup)[0];
  console.log("Searching for holiday: " + holidayName);
  /* Lookup the date for the holiday on every year from 'startYear' (1990) to today */
  for (let year = startYear; year <= endYear; year++) {
    console.log("Getting year: " + year);
    let holiday;

    if (!date) {
      holiday = moment(year + "-01-01", "YYYY-MM-DD").holiday(req.params.holiday);
      if (!holiday) {
        /* 'Leap Year' doesn't exist every year... */
        continue;
      }

      /*
      * NOTE: Memorial Day and Labor Day are two special cases -- the holiday is a Monday,
      * however the entire weekend typically is holidy-esque. Account for that below.
      *
      * For those that have 'eve' celebrations, include those too.
      *
      * We (should) could expand this to account for Fri or Mon on the 4th of July or the
      * entire weekend if it occurs on a Thu or Tues */

      let extraDays = 0;
      switch (req.params.holiday.toLowerCase()) {
      case 'labor day':
      case 'memorial day':
        extraDays = -2; /* Include two days prior */
        break;
      case 'christmas day':
      case 'new year\'s day':
        extraDays = -1; /* Include 'Eve' */
        break;
      }
      let direction = extraDays < 0 ? -1 : 1;

      for (let i = 0; i <= Math.abs(extraDays); i++) {
        let comparison = "strftime('%Y-%m-%d',taken)='" + holiday.format("YYYY-MM-DD") + "'";
        /* If no holiday has been set yet, start the comparison function passed to WHERE
        * otherwise append it with OR. */
        if (!dayIsHoliday) {
          dayIsHoliday = comparison;
        } else {
          dayIsHoliday += " OR " + comparison;
        }

        holiday.date(holiday.date() + direction);
      }
    } else {
      let comparison = "strftime('%Y-%m-%d',taken)='" 
        + moment(year + "-" + date.month + "-" + date.day, "YYYY-MM-DD").format("YYYY-MM-DD")
        + "'";
      if (!dayIsHoliday) {
        dayIsHoliday = comparison;
      } else {
        dayIsHoliday += " OR " + comparison;
      }
    }
  }

  let query = "SELECT photos.*,albums.path AS path FROM photos " +
    "INNER JOIN albums ON (albums.id=photos.albumId) " +
    "WHERE (photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL AND (" + dayIsHoliday + ")) " +
    "ORDER BY strftime('%Y-%m-%d', taken) DESC,id DESC";

  return photoDB.sequelize.query(query, {
    type: photoDB.Sequelize.QueryTypes.SELECT
  }).then(function(photos) {
    photos.forEach(function(photo) {
      for (var key in photo) {
        if (photo[key] instanceof Date) {
          photo[key] = moment(photo[key]);
        }
      }
    });

    return res.status(200).json({
      holiday: holidayName,
      items: photos
    });

  }).catch(function(error) {
    console.error("Query failed: " + query);
    return Promise.reject(error);
  });
});


/* Each photos has:

* locations
* people
* date
* tags
* photo info

*/
router.get("/memories/:date?", function(req, res/*, next*/) {
  let limit = parseInt(req.query.limit) || 50,
    id, cursor, index;

  if (req.query.next) {
    let parts = req.query.next.split("_");
    cursor = new Date(parts[0]);
    id = parseInt(parts[1]);
  } else {
    cursor = "";
    id = -1;
  }

  if (id == -1) {
    index = "strftime('%m-%d',taken)=strftime('%m-%d',:date)";
  } else {
    index = "((strftime('%Y-%m-%d',taken)=strftime('%Y-%m-%d',:cursor) AND photos.id<"+id+ ") OR " + 
    "(strftime('%m-%d',taken)=strftime('%m-%d',:cursor) AND strftime('%Y',taken)<strftime('%Y',:cursor)))";
  }

  let date = new Date("2016-" + req.params.date);
  let query = "SELECT photos.*,albums.path AS path FROM photos " +
    "INNER JOIN albums ON (albums.id=photos.albumId) " +
    "WHERE (photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL AND " + index + ") " +
    "ORDER BY strftime('%Y-%m-%d',taken) DESC,id DESC LIMIT " + (limit + 1);

//  console.log("Memories for " + date.toISOString().replace(/^2016-(.*)T.*$/, "$1"));
//  if (cursor) {
//    console.log("Cursor" + cursor.toISOString().replace(/T.*/, ""));
//  }

  return photoDB.sequelize.query(query, {
    replacements: {
      cursor: cursor,
      date: date
    },
    type: photoDB.Sequelize.QueryTypes.SELECT
  }).then(function(photos) {
    photos.forEach(function(photo) {
      for (var key in photo) {
        if (photo[key] instanceof Date) {
          photo[key] = moment(photo[key]);
        }
      }
    });

    let more = photos.length > limit; /* We queried one extra item to see if there are more than LIMIT available */

    let last;
    if (more) {
      photos.splice(limit);
      last = photos[photos.length - 1];
    }

    let results = {
      items: photos.sort(function(a, b) {
        return new Date(b.taken) - new Date(a.taken);
      })
    };

    if (more) {
      results.cursor = new Date(last.taken).toISOString().replace(/T.*/, "") + "_" + last.id;
      results.more = true;
    }
    return res.status(200).json(results);
  }).catch(function(error) {
    console.error("Query failed: " + query);
    return Promise.reject(error);
  });
});

router.get("/duplicates", function(req, res/*, next*/) {
  let replacements = {};

  return photoDB.sequelize.query(
      "SELECT filename,COUNT(*) AS count FROM photos WHERE photos.duplicate=0 AND photos.deleted!=1 " +
      "GROUP BY filename HAVING count > 1", {
    replacements: replacements,
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(duplicates) {
    let filenames = [];
    duplicates.forEach(function(duplicate) {
      filenames.push(duplicate.filename);
    });
    replacements.filenames = filenames;

    return photoDB.sequelize.query(
      "SELECT photos.*,albums.path AS path,photohashes.hash,(albums.path || photos.filename) AS filepath FROM photos " +
      "LEFT JOIN albums ON albums.id=photos.albumId " +
      "LEFT JOIN photohashes ON photohashes.photoId=photos.id " +
      "WHERE filename IN (:filenames) ORDER BY photos.filename,photos.width,photos.height,photos.size DESC", {
      replacements: replacements,
      type: photoDB.Sequelize.QueryTypes.SELECT,
      raw: true
    }).then(function(photos) {
      return res.status(200).json({
        items: photos
      });
    });
  }).catch(function(error) {
    return Promise.reject(error);
  });
});

router.get("/trash", function(req, res/*, next*/) {
  return photoDB.sequelize.query(
    "SELECT photos.*,albums.path AS path,(albums.path || photos.filename) AS filepath FROM photos " +
    "LEFT JOIN albums ON albums.id=photos.albumId " +
    "WHERE deleted=1 ORDER BY photos.updated DESC", {
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(photos) {
    return res.status(200).json({
      items: photos
    });
  });
});

function getFacesForPhoto(id) {
  return photoDB.sequelize.query("SELECT * FROM faces WHERE photoId=:id", {
    replacements: {
      id: id
    },
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then((faces) => {
    return Promise.map(faces, (face) => {
      return photoDB.sequelize.query(
        "SELECT face1Id,face2Id " +
        "FROM facedistances " +
        "WHERE distance<=0.5 AND (face1Id=:id OR face2Id=:id)", {
        replacements: {
          id: face.id
        },
        type: photoDB.Sequelize.QueryTypes.SELECT,
        raw: true
      }).then((faceIds) => {
        return photoDB.sequelize.query(
          "SELECT photos.id,faces.id AS faceId,fd.distance,albums.path,photos.filename " +
          "FROM faces " +
          "INNER JOIN photos ON photos.id=faces.photoId " +
          "INNER JOIN albums ON albums.id=photos.albumId " +
          "INNER JOIN facedistances AS fd ON " +
          "( " +
          "   (fd.face1Id=faces.id AND fd.face2Id=:faceId) " +
          "OR (fd.face2Id=faces.id AND fd.face1Id=:faceId) " +
          ") " +
          "WHERE faces.id IN (:ids) " +
          "ORDER BY fd.distance ASC", {
          replacements: {
            ids: faceIds.map((match) => {
              return (match.face1Id == face.id) ? match.face2Id : match.face1Id;
            }),
            faceId: face.id
          },
          type: photoDB.Sequelize.QueryTypes.SELECT,
          raw: true
        });
      }).then((photos) => {
        face.relatedPhotos = photos.filter((photo) => { return photo.id != id }).map((photo) => {
          return {
            id: photo.id,
            distance: photo.distance,
            faceId: photo.faceId,
            path: photo.path + photo.filename
          };
        });
      });
    }).then(() => {
      return faces;
    });
  });
}
router.get("/faces/:id", (req, res) => {
  const id = parseInt(req.params.id);
  if (id != req.params.id) {
    return res.status(400).send({ message: "Usage faces/:id"});
  }
  return getFacesForPhoto(id).then((faces) => {
    return res.status(200).json(faces);      
  });
});

router.get("/random/:id?", (req, res) => {
  let id = parseInt(req.params.id),
    filter = "";

  if (id == req.params.id) {
    console.log("GET /random/" + id);
    filter = "AND id=:id";
  } else {
    filter = "AND faces>0";
    id = undefined;
  }

  return photoDB.sequelize.query("SELECT id,duplicate FROM photos WHERE deleted=0 " + filter, {
    replacements: {
      id: id
    },
    type: photoDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then((results) => {
    if (!results.length) {
      return [];
    }
    if (id) {
      if (results[0].duplicate) {
        id = results[0].duplicate;
      }
    } else {
      id = results[Math.floor(Math.random() * results.length)].id;
    }

    return photoDB.sequelize.query(
      "SELECT photos.*,albums.path AS path FROM photos " +
      "INNER JOIN albums ON albums.id=photos.albumId " +
      "WHERE photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL AND photos.id=:id", {
      replacements: {
        id: id,
      },
      type: photoDB.Sequelize.QueryTypes.SELECT,
      raw: true
    });
  }).then(function(photos) {
    if (!photos.length) {
      return res.status(404).send(id + " not found.");
    }
    const photo = photos[0];
    for (var key in photo) {
      if (photo[key] instanceof Date) {
        photo[key] = moment(photo[key]);
      }
    }
    return getFacesForPhoto(photo.id).then((faces) => {
      photo.faces = faces;
      return res.status(200).json(photo);
    })
  });
})

router.get("/mvimg/*", function(req, res/*, next*/) {
  let limit = parseInt(req.query.limit) || 50,
    id, cursor, index;

  if (req.query.next) {
    let parts = req.query.next.split("_");
    cursor = parts[0];
    id = parseInt(parts[1]);
  } else {
    cursor = "";
    id = -1;
  }

  if (id == -1) {
    index = "";
  } else {
    index = "AND ((strftime('%Y-%m-%d',taken)=strftime('%Y-%m-%d',:cursor) AND photos.id<:id) OR " + 
      "strftime('%Y-%m-%d',taken)<strftime('%Y-%m-%d',:cursor))";
  }

  console.log(req.url);
  let path = decodeURI(req.url).replace(/\?.*$/, "").replace(/^\//, "").replace(/^mvimg\//, ""),
    query = "SELECT photos.*,albums.path AS path FROM photos " +
      "INNER JOIN albums ON (albums.id=photos.albumId AND albums.path LIKE :path) " +
      "WHERE (photos.name LIKE 'MVIMG%' AND photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL) " + index + " " +
      "ORDER BY strftime('%Y-%m-%d',taken) DESC,id DESC LIMIT " + (limit + 1),
    replacements = {
      id: id,
      cursor: cursor,
      path: path + "%"
    };
console.log("Trying path as: " + path);
//  console.log("Fetching from: " + JSON.stringify(replacements, null, 2) + "\nwith:\n" + query);
  
  return photoDB.sequelize.query(query, {
    replacements: replacements,
    type: photoDB.Sequelize.QueryTypes.SELECT
  }).then(function(photos) {
    photos.forEach(function(photo) {
      for (var key in photo) {
        if (photo[key] instanceof Date) {
          photo[key] = moment(photo[key]);
        }
      }
    });

    let more = photos.length > limit; /* We queried one extra item to see if there are more than LIMIT available */

//    console.log("Requested " + limit + " and matched " + photos.length);

    let last;
    if (more) {
      photos.splice(limit);
      last = photos[photos.length - 1];
    }

    let results = {
      items: photos.sort(function(a, b) {
        return new Date(b.taken) - new Date(a.taken);
      })
    };

    if (more) {
      results.cursor = new Date(last.taken).toISOString().replace(/T.*/, "") + "_" + last.id;
      results.more = true;
    }
    return res.status(200).json(results);
  }).catch(function(error) {
    console.error("Query failed: " + query);
    return Promise.reject(error);
  });
});

router.get("/*", function(req, res/*, next*/) {
  let limit = parseInt(req.query.limit) || 50,
    id, cursor, index;

  if (req.query.next) {
    let parts = req.query.next.split("_");
    cursor = parts[0];
    id = parseInt(parts[1]);
  } else {
    cursor = "";
    id = -1;
  }

  if (id == -1) {
    index = "";
  } else {
    index = "AND ((strftime('%Y-%m-%d',taken)=strftime('%Y-%m-%d',:cursor) AND photos.id<:id) OR " + 
      "strftime('%Y-%m-%d',taken)<strftime('%Y-%m-%d',:cursor))";
  }

  let path = decodeURI(req.url).replace(/\?.*$/, "").replace(/^\//, ""),
    query = "SELECT photos.*,albums.path AS path FROM photos " +
      "INNER JOIN albums ON (albums.id=photos.albumId AND albums.path LIKE :path) " +
      "WHERE (photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL) " + index + " " +
      "ORDER BY strftime('%Y-%m-%d',taken) DESC,id DESC LIMIT " + (limit + 1),
    replacements = {
      id: id,
      cursor: cursor,
      path: path + "%"
    };

//  console.log("Fetching from: " + JSON.stringify(replacements, null, 2) + "\nwith:\n" + query);
  
  return photoDB.sequelize.query(query, {
    replacements: replacements,
    type: photoDB.Sequelize.QueryTypes.SELECT
  }).then(function(photos) {
    photos.forEach(function(photo) {
      for (var key in photo) {
        if (photo[key] instanceof Date) {
          photo[key] = moment(photo[key]);
        }
      }
    });

    let more = photos.length > limit; /* We queried one extra item to see if there are more than LIMIT available */

//    console.log("Requested " + limit + " and matched " + photos.length);

    let last;
    if (more) {
      photos.splice(limit);
      last = photos[photos.length - 1];
    }

    let results = {
      items: photos.sort(function(a, b) {
        return new Date(b.taken) - new Date(a.taken);
      })
    };

    if (more) {
      results.cursor = new Date(last.taken).toISOString().replace(/T.*/, "") + "_" + last.id;
      results.more = true;
    }
    return res.status(200).json(results);
  }).catch(function(error) {
    console.error("Query failed: " + query);
    return Promise.reject(error);
  });
});

module.exports = router;
