import Service from './service';
import EventsMixin from './mixin/events';
import prettyBytes from 'pretty-bytes';

class File {
  constructor(options) {
    this.id = ++File.guid;

    Object.assign(this, File.defaults, options);
  }
}

File.defaults = {
  lastModified: undefined,
  state: 'complete',
  thumbnail: undefined,
  url: undefined,
  error: undefined,
  name: undefined,
  progress: 0,
  size: 0,
  type: '*',
  uploadFile: undefined,
};
File.guid = 0;

const parseType = type => type.indexOf('/') > 0 ? type.slice(0, type.indexOf('/')) : type;

// const root = "http://127.0.0.1:8091"; // for local development
const root = ''; // (empty) for running on https://widgetic.com/upload

export default class UploadService extends EventsMixin(Service) {
  constructor(user, token) {
    super();

    this.user = user.id;
    this.token = token;

    this.refreshed = {};

    this.files = [];

    // max file size in MB
    this.maxFilesize = user.premium ? 250 : 25; // MB limit / file for premium / free plan

    // chunk size in MB
    this.chunkSize = 90; // MB to go over Cloudflare's 100MB limit

    // max quota in bytes
    this.quota = (user.premium ? 10000 : 250) * 1000 * 1000; // total quota for premium / free plan
    
    // used quota in bytes
    this.used = 0;

    this.events = ['update', 'complete'];
  }

  getMaxFileSize() {
    return this.maxFilesize;
  }

  getChunkSize() {
    return this.chunkSize;
  }

  getUploadURL() {
    let url = root + '/upload?user=' + this.user;

    // console.log("getUploadURL:", url);

    return url;
  }

  getAuthHeaders() {
    return { 'w-utoken': this.token };
  }

  getFiles(type) {
    // console.log("getting files:", this.files);
    type = parseType(type);

    // if files for this type were already fetched, return them
    if (this.refreshed[type])
      return Promise.resolve(this.files.filter(f => f.type === type));

    // otherwise fetch them
    // console.log("fetching files for type:", type);
    return this._fetch(type)
      .then(files => {
        // console.log('fetched files:', files)
        return this.files.push.apply(this.files, files)
      })
      .then(this._broadcast.update)
      .then(_ => this.files.filter(f => f.type === type));
  }

  get(id) {
    return this.files.filter(file => file.id === id)[0];
  }

  getUser() {
    return this.user;
  }

  // when the upload starts: create a new File instance and push it to the files array
  upload(uploadFile) {
    console.log("Uploads Service onUploadStart:", uploadFile);

    // create a new File instance from the uploadFile 
    let file = new File({
      state: 'new',
      size: uploadFile.size,
      name: uploadFile.name,
      type: parseType(uploadFile.type),
      lastModified: Date.now(),
      uploadFile,
    });
    // console.log("on upload file:", file);

    // push it to the files array
    this.files.push(file);

    // increase used quota
    this.used += uploadFile.size;

    // broadcast the update of the files array
    this._broadcast.update();
  }

  // when the upload is complete: update the file in the files array with the data from the server
  uploadComplete(uploadFile, instanceId) {
    // console.log("Uploads Service uploadComplete:", uploadFile);

    // if the upload failed, show an error
    if (uploadFile.status === 'error') return;

    // read the response string from the request
    let responseText = uploadFile.xhr && uploadFile.xhr.response ? uploadFile.xhr.response : "";
    if(!responseText) {
      console.error("Error parsing uploadComplete response:", error);
      return this.setError(uploadFile, 'Error uploading file!');
    }

    // read server file from the response
    const response = JSON.parse(responseText);
    let serverFile = response.files.data[0];
    // console.log("uploadComplete file on the server:", serverFile);

    // read quota from response to update the used quota in dropzone
    // this.quota = response.quota.max;
    this.used = response.quota.used;

    // console.log("Upload Service uploadComplete update file to complete:", uploadFile, serverFile);

    // update the file in the files array with the data from the server
    this._update(uploadFile, f => {
      f.state = 'complete';
      f.id = serverFile.id;
      f.path = serverFile.path;
      f.url = response.hostUrl + serverFile.path;
      f.lastModified = serverFile.lastModified;
    });

    // broadcast the completion of the upload based on the fileId and instanceId
    this._broadcast.complete([this.get(serverFile.id), instanceId]);
  }

  accept(file, done) {
    // console.log("Editor accept file:", file, done);

    // if with the file size the quota is not exceeded, accept the file
    // this.used was increased by adding file.size in the `upload` method
    if (this.used < this.quota) {
      return done();
    }

    // if the file exceeds the quota show an error
    done(
      `The file exceeds the quota by ${prettyBytes(this.used - this.quota)}.`
    );
  }

  remove(file) {
    this.files = this.files.filter(f => f.id !== file.id);
    this.used -= file.size;

    // if uploaded, also remove from the server
    if (file.state === 'complete') this._remove(file);

    this._broadcast.update();
  }

  setThumbnail(file, thumbnail) {
    console.log("Uploads Service setThumbnail:", file, thumbnail);
    this._update(file, f => f.thumbnail = thumbnail);
  }

  setError(file, error) {
    this.used -= file.size;

    this._update(file, f => { f.error = error; f.size = 0; });
  }

  setUploadProgress(file, percentage, size) {
    this._update(file, f => f.progress = percentage);
    // console.log("Uploads Service setUploadProgress:", percentage, file.state);
  }

  setUploadState(file, state) {
    // console.log("Uploads Service setUploadState:", state);
    this._update(file, f => f.state = state);
  }

  _update(file, transform) {

    // console.log("Uploads Service _update file:", transform);

    // apply the transform function to the file in the array
    this.files = this.files.map(f => {
      let fl = f.uploadFile ? f.uploadFile : f;

      let equal = fl.name === file.name && 
                  fl.size === file.size && 
                  fl.lastModified === file.lastModified;

      // console.log("Uploads Service _update compare:", equal, file, fl);

      // if the file is the one to update, apply the transform function
      // if (f.uploadFile === file)
      if(equal) 
      {
        // console.log("Uploads Service _update file found:", file, fl);
        transform(f);
        // console.log("Uploads Service _update file found:", f);
      }

      return f;
    });

    // broadcast the update of the files array
    this._broadcast.update();
  }

  // fetch files from the server for the user
  _fetch(type) {
    // console.log("fetching files for type:", type);
    return jQuery
      .ajax({
        url: root + `/upload/?user=${this.user}&type=${type}`,
        headers: this.getAuthHeaders(),
      })
      .then(response => {
        // console.log("fetched files response:", response);
        this.refreshed[type] = true;

        // read quota from response
        // this.quota = response.quota.max;
        this.used = response.quota.used;

        return response.files.data.map(file => {

          // console.log("fetched file:", file);

          const type = parseType(file.mime);
          const url = response.hostUrl + file.path;
          const thumbnail = type === 'image' ? url : undefined;

          // return a new File instance
          return new File({
            id: file.id,
            type,
            url,
            thumbnail,
            path: file.path,
            name: file.name,
            size: file.size,
            lastModified: file.lastModified,
          });
        });
      });
  }

  _remove(file) {
    return jQuery
      .ajax({
        method: 'DELETE',
        url: `/upload/?user=${this.user}&filePath=${file.path}`,
        headers: this.getAuthHeaders(),
      })
      .then(response => {
        // TODO update quota after UP-3 is done
      })
      .then(this._broadcast.update)
      .catch(console.error.bind(console));
  }
}
