import config from './config';
import * as Errors from './Errors';

class Api {
  constructor() {
  }

  baseUrl() { return config.baseApiUrl; }

  url(path) {
    return this.baseUrl() + path;
  }

  checkStatus(response) {
    if (response.status == 204) {
      return null;
    } else if (response.ok) {
      return response.json();
    } else if (response.status === 404) {
      throw new Errors.ApiNotFoundError(response.statusText, response);
    } else if (response.status === 422) {
      return response.json().then(json => { throw new Errors.ApiValidationError(null, response, json) }, jsonErr => { throw new Errors.ApiValidationError(null, response, null) });
    } else {
      throw new Errors.ApiServerError(response.statusText || "Unknown Server Error", response);
    }
  }

  performRequest(url, method, params = {}, headers = {}) {
    const hasBody = Object.keys(params || {}).length !== 0;

    const reqHeaders = new Headers();
    reqHeaders.append('Accept', 'application/json');
    reqHeaders.append('Content-Type', 'application/json');

    for (let key in headers) {
      reqHeaders.append(key, headers[key]);
    }

    const opts = {
      headers: reqHeaders,
      method: method,
      credentials: "same-origin"
    };

    if (hasBody) {
      opts.body = JSON.stringify(params);
    }

    return fetch(url, opts).then(this.checkStatus);
  }

  objectToUrlParams(obj, queryParams = [], prefixes = []) {
    for (let key in obj) {
      const val = obj[key];
      const paramName = prefixes.join("[") + "[".repeat(Math.min(prefixes.length, 1)) + encodeURIComponent(key) + "]".repeat(prefixes.length);
      if (Array.isArray(val)) {
        for (let x of val) {
          queryParams.push(paramName + "[]=" + (x === null ? '' : encodeURIComponent(x)));
        }
      } else if (typeof(val) === "object") {
        this.objectToUrlParams(val, queryParams, prefixes.concat([key]));
      } else {
        queryParams.push(paramName + "=" + (val === null ? '' : encodeURIComponent(val)));
      }
    }

    return queryParams;
  }

  buildGetUrl(url, params = {}) {
    const queryParams = this.objectToUrlParams(params);
    if (queryParams.length) {
      url = url + "?" + queryParams.join("&");
    }
    return url;
  }

  get(url, params = {}) {
    url = this.buildGetUrl(url, params);

    return this.performRequest(url, "GET");
  }

  cacheFirstGet(url, params = {}, dataHandler) {
    url = this.buildGetUrl(url, params);
    let networkDataReceived = false;

    const networkUpdate = this.performRequest(url, "GET", {}, {"Cache-Then-Network": "true"})
      .then(data => {
        networkDataReceived = true;
        return dataHandler(data);
      });

    return caches.match(url)
      .then(response => {
        if (!response) throw Error("No data");
        return response.json();
      })
      .then(data => {
        // don't overwrite newer network data
        if (!networkDataReceived) {
          dataHandler(data);
        }
      })
      .catch(function() {
        // we didn't get cached data, the network is our last hope:
        return networkUpdate;
      });
  }

  post(url, params = {}) {
    return this.performRequest(url, "POST", params);
  }

  patch(url, params = {}) {
    return this.performRequest(url, "PATCH", params);
  }

  del(url, params = {}) {
    return this.performRequest(url, "DELETE", params);
  }

  getRecipeList(page, per, sortColumn, sortDirection, name, tags, dataHandler) {
    const params = {
      criteria: {
        page: page || 1,
        per: per || 20,
        sort_column: sortColumn || null,
        sort_direction: sortDirection || null,
        name: name || null,
        tags: tags || null
      }
    };

    return this.cacheFirstGet("/recipes", params, dataHandler);
  }

  getRecipe(id, scale = null, system = null, unit = null, dataHandler) {
    const params = {
      scale,
      system,
      unit
    };

    return this.cacheFirstGet("/recipes/" + id, params, dataHandler);
  }

  buildRecipeParams(recipe) {
    const params = {
      recipe: {
        name: recipe.name,
        description: recipe.description,
        source: recipe.source,
        yields: recipe.yields,
        is_ingredient: recipe.is_ingredient,
        total_time: recipe.total_time,
        active_time: recipe.active_time,
        step_text: recipe.step_text,
        tag_names: recipe.tags,
        recipe_ingredients_attributes: recipe.ingredients.map(i => {
          if (i._destroy) {
            return {
              id: i.id,
              _destroy: true
            };
          } else {
            return {
              id: i.id,
              name: i.name,
              ingredient_id: i.ingredient_id,
              quantity: i.quantity,
              units: i.units,
              preparation: i.preparation,
              sort_order: i.sort_order
            };
          }
        })
      }
    };
    return params;
  }

  patchRecipe(recipe) {
    return this.patch("/recipes/" + recipe.id, this.buildRecipeParams(recipe));
  }

  postRecipe(recipe) {
    return this.post("/recipes/", this.buildRecipeParams(recipe));
  }

  deleteRecipe(id) {
    return this.del("/recipes/" + id);
  }

  postPreviewSteps(step_text) {
    const params = {
      step_text: step_text
    };

    return this.post("/recipes/preview_steps", params);
  }

  getSearchIngredients(query) {
    const params = { query: query };
    return this.get("/ingredients/search", params);
  }

  getCalculate(input, output_unit, ingredient_id, density) {
    const params = {
      input,
      output_unit,
      ingredient_id,
      density
    };
    return this.get("/calculator/calculate", params);
  }

  getFoodList(page, per, name) {
    const params = {
      page,
      per,
      name
    };

    return this.get("/foods/", params);
  }

  getFood(id) {
    return this.get("/foods/" + id);
  }

  buildFoodParams(food) {
    return {
      food: {
        name: food.name,
        notes: food.notes,
        ndbn: food.ndbn,
        density: food.density,

        water: food.water,
        ash: food.ash,
        protein: food.protein,
        kcal: food.kcal,
        fiber: food.fiber,
        sugar: food.sugar,
        carbohydrates: food.carbohydrates,
        calcium: food.calcium,
        iron: food.iron,
        magnesium: food.magnesium,
        phosphorus: food.phosphorus,
        potassium: food.potassium,
        sodium: food.sodium,
        zinc: food.zinc,
        copper: food.copper,
        manganese: food.manganese,
        vit_c: food.vit_c,
        vit_b6: food.vit_b6,
        vit_b12: food.vit_b12,
        vit_a: food.vit_a,
        vit_e: food.vit_e,
        vit_d: food.vit_d,
        vit_k: food.vit_k,
        cholesterol: food.cholesterol,
        lipids: food.lipids,


        food_units_attributes: food.food_units.map(fu => {
          if (fu._destroy) {
            return {
              id: fu.id,
              _destroy: true
            };
          } else {
            return {
              id: fu.id,
              name: fu.name,
              gram_weight: fu.gram_weight
            };
          }
        })
      }
    }
  }

  postFood(food) {
    return this.post("/foods/", this.buildFoodParams(food));
  }

  patchFood(food) {
    return this.patch("/foods/" + food.id, this.buildFoodParams(food));
  }

  deleteFood(id) {
    return this.del("/foods/" + id);
  }

  postIngredientSelectNdbn(ingredient) {
    const url = ingredient.id ? "/foods/" + ingredient.id + "/select_ndbn" : "/foods/select_ndbn";
    return this.post(url, this.buildFoodParams(ingredient));
  }

  getUsdaFoodSearch(query) {
    return this.get("/foods/usda_food_search", {query: query});
  }

  getNoteList() {
    return this.get("/notes/");
  }

  postNote(note) {
    const params = {
      content: note.content
    };

    return this.post("/notes/", params);
  }

  deleteNote(note) {
    return this.del("/notes/" + note.id);
  }

  getLogList(page, per) {
    const params = {
      page,
      per
    };

    return this.get("/logs", params);
  }

  getLog(id) {
    return this.get("/logs/" + id);
  }

  buildLogParams(log) {
    const recParams = this.buildRecipeParams(log.recipe);

    return {
      log: {
        date: log.date,
        rating: log.rating,
        notes: log.notes,
        source_recipe_id: log.source_recipe_id,
        recipe_attributes: recParams.recipe
      }
    };
  }

  postLog(log) {
    const params = this.buildLogParams(log);
    const rec = params.log.recipe_attributes;
    if (rec && rec.recipe_ingredients_attributes) {
      rec.recipe_ingredients_attributes.forEach(ri => ri.id = null);
    }
    return this.post("/recipes/" + log.original_recipe_id + "/logs/", params);
  }

  patchLog(log) {
    return this.patch("/logs/" + log.id, this.buildLogParams(log));
  }

  getTaskLists(dataHandler) {
    return this.cacheFirstGet("/task_lists/", {}, dataHandler);
  }

  buildTaskListParams(taskList) {
    return {
      task_list: {
        name: taskList.name
      }
    };
  }

  postTaskList(taskList) {
    const params = this.buildTaskListParams(taskList);
    return this.post("/task_lists/", params);
  }

  patchTaskList(taskList) {
    const params = this.buildTaskListParams(taskList);
    return this.patch(`/task_lists/${taskList.id}`, params);
  }

  deleteTaskList(taskList) {
    return this.del(`/task_lists/${taskList.id}`);
  }

  buildTaskItemParams(taskItem) {
    return {
      task_item: {
        name: taskItem.name,
        quantity: taskItem.quantity,
        completed: taskItem.completed
      }
    }
  }

  postTaskItem(listId, taskItem) {
    const params = this.buildTaskItemParams(taskItem);
    return this.post(`/task_lists/${listId}/task_items`, params);
  }

  patchTaskItem(listId, taskItem) {
    const params = this.buildTaskItemParams(taskItem);
    return this.patch(`/task_lists/${listId}/task_items/${taskItem.id}`, params);
  }

  deleteTaskItems(listId, taskItems) {
    const params = {
      ids: taskItems.map(i => i.id)
    };
    return this.del(`/task_lists/${listId}/task_items/`, params);
  }

  completeTaskItems(listId, taskItems, invert = false) {
    const params = {
      ids: taskItems.map(i => i.id)
    };

    if (invert === true) {
      params.invert = true;
    }

    return this.patch(`/task_lists/${listId}/task_items/complete`, params);
  }
  
  addRecipeToTaskList(listId, recipeId) {
    return this.patch(`/task_lists/${listId}/add_recipe/${recipeId}`);
  }

  getAdminUserList() {
    return this.get("/admin/users");
  }

  postUser(userObj) {
    const params = {
      user: {
        username: userObj.username,
        full_name: userObj.full_name,
        email: userObj.email,
        password: userObj.password,
        password_confirmation: userObj.password_confirmation
      }
    };

    return this.post("/user/", params);
  }

  patchUser(userObj) {
    const params = {
      user: {
        username: userObj.username,
        full_name: userObj.full_name,
        email: userObj.email,
        password: userObj.password,
        password_confirmation: userObj.password_confirmation
      }
    };

    return this.patch("/user/", params);
  }

  postLogin(username, password) {
    const params = {
      username: username,
      password: password
    };

    return this.post("/login", params);
  }

  getLogout() {
    return this.get("/logout");
  }

  getCurrentUser() {
    return this.get("/user")
  }
}

const api = new Api();

export default api;
