# The Resource API. This API has only 3 methods: create, confirm and destroy.
#
# To create a new resource, call the create method with a filename and a
# content_type, you will get a resource as a result. This resource has an
# upload_url you can use to put your content. Caution, this url is valid only
# for 2 minutes.
#
# When uploading a resource, two http headers are *mandatory*:
# * Content-Type, which *must* be the same value as the one used to create the
#   resource
# * Set the acl to public read. Eg. on AWS send the x-amz-acl header set to
#   "public-read". This ensures that the resource is publicly readable.
#
# The upload can fail if these two flags/settings are missing.
#
# Once you finished uploading the resource, you can confirm it with the method
# confirm. You MUST confirm a resource in order to use it elsewhere (eg. in the
# Event api). Once confirmed, a resource can't be updated. For example, if you
# want to update the resource content, create a new one. A resource has these
# fields:
#
# id - the resource's id
# name - the resource's name
# upload_url - the secured url where you can upload your content. This link has
#              already all the correct parameters for S3. Do not change them.
# url - the public url where you can read the content
# h264_url - the url of the h264 file. Only for video resources
# mpeg_url - the url of the mpeg file. Only for video resources
# webm_url - the url of the webm file. Only for video resources
# thumbnail_urls - the urls of the thumbnails. Only for video resources
# encoding_progress - the progress of the encoding in %. Only for video
#                     resources
#
# Examples
#
# {
#   "resource": {
#     "id": 1,
#     "upload_url": "https:...",
#     "url": "https:..."
#   }
# }
#
# {
#   "resource": {
#     "id": 42145,
#     "name": "Resource 6",
#     "url": "https://...",
#     "h264_url": "https://...",
#     "mpeg_url": "https://...",
#     "webm_url": "https://...",
#     "thumbnail_urls": [
#       "https://...",
#       "https://...",
#       "https://...",
#       "https://...",
#       "https://..."
#     ],
#     "encoding_progress": 0
#   }
# }
module Fogged
  class ResourcesController < Fogged.parent_controller.constantize
    before_action :select_resourceables, :only => :index
    before_action :select_resource, :only => [:confirm, :destroy, :show, :update]
    skip_before_action :verify_authenticity_token, :only => :zencoder_notification

    # List all resources. Parameter type is mandatory. It indicates in which
    # "context" you want all resources. You can refine the search, using
    # using parameters type_id or type_ids, which will select the context
    # objects before retrieving the resources. Pagination options are
    # available. Search options are available.
    #
    # type - the context/type in which the resources search is done. Mandatory
    # type_id - the specific type id
    # type_ids - the specific type ids
    # query - text search (insensitive) on the resource's name
    #
    # Example
    #
    # GET /api/v1/resources?type=group&ids[]=2&ids[]=3
    # GET /api/v1/resources?type=group
    #
    # Returns an array of Resources found
    # Raises 400 if the given type is unknown
    # Raises 500 if an error occurs
    def index
      resources = @resourceables.map do |resourceable|
        resourceable.resources.search(params)
      end

      render :json => paginate(resources.flatten.uniq),
             :meta => @meta,
             :each_serializer => ResourceSerializer
    end

    # Get the resource.
    #
    # id - the resource's id
    #
    # Example
    #
    # GET /api/v1/resources/123
    #
    # Returns the Resource
    # Raises 404 if the Resource is not found
    # Raises 500 if an error occurs
    def show
      render :json => @resource,
             :serializer => ResourceSerializer
    end

    # Create a new resource. You must provide a filename (example: foo.png)
    # and a content_type (example: "image/png"). This content_type must be the
    # same as the one used with the upload_url.
    #
    # resource - a hash with a name (optional), a filename and a content_type
    #
    #
    # Examples
    #
    # POST /api/v1/resources
    # {
    #   "resource": {
    #     "name": "The ultimate ruby"
    #     "filename": "ruby.png"
    #     "content_type": "image/png"
    #   }
    # }
    #
    # Return the created resource which will be marked as uploading.
    # Raises 400 if resource params are not valid.
    # Raises 500 if an error occurs.
    def create
      resource_attributes = resource_params.except(:filename)
      resource_attributes[:extension] = extension(resource_params.require(:filename))

      resource = Resource.create!(resource_attributes.merge(:uploading => true))

      render :json => resource,
             :serializer => ResourceSerializer,
             :include_upload_url => true
    end

    # Update a Resource. You can update the name of a Resource or its context
    # object
    #
    # id - the resource's id
    # resource - the resource params
    #
    # Example
    #
    # PUT /api/v1/resources/1
    # {
    #   "resource": {
    #     "name": "Updated"
    #   }
    # }
    #
    # Returns the updated Resource
    # Raises 400 if the parameters are invalid
    # Raises 404 if the resource or the context object can't be found
    # Raises 500 if an error occurs
    def update
      @resource.update!(resource_params.permit(:name))

      show
    end

    # Confirm a resource. You MUST confirm a resource before using it.
    #
    # id - the resource id
    #
    # Examples
    #
    # PUT /api/v1/resources/1/confirm
    #
    # Return the confirmed resource. Note that the upload_url will not be
    # present in the response.
    # Raises 500 if a server errors occurs
    def confirm
      @resource.process!
      @resource.update!(:uploading => false)
      show
    end

    # Destroy a resource. You can choose to destroy a resource. This
    # will also destroy the content on S3.
    #
    # id - the resource id
    #
    # Examples
    #
    # DELETE /api/v1/resources/1
    #
    # Return an empty response with 204(No content)
    # Raises 500 if a server errors occurs
    def destroy
      @resource.destroy!
      head :no_content
    end

    def zencoder_notification
      if (resource = Resource.find_by(:encoding_job_id => job_params[:id])) &&
         (file = params[:outputs].try(:first)) &&
         job_params[:state] == "finished"

        resource.update!(
          :encoding_progress => 100,
          :width => file[:width],
          :height => file[:height],
          :duration => file[:duration_in_ms].to_f / 1000.0
        )
      end

      head :ok
    end

    private

    def select_resourceables
      ids = params[:type_id] || params[:type_ids]
      ids = [ids] unless ids.respond_to?(:to_ary)
      @resourceables = resourceable_clazz.all
      @resourceables = resourceable_clazz.where(:id => ids) if ids.try(:any?)
    end

    def resourceable_clazz
      @_resourceable_clazz ||= resource_type_param.try(:classify)
                               .try(:safe_constantize)
      unless @_resourceable_clazz
        fail(ArgumentError, "Unknown resourceable type: #{params[:type]}")
      end
      @_resourceable_clazz
    end

    def resource_type_param
      params.require(:type)
    end

    def select_resource
      @resource = Resource.find(params[:id])
    end

    def resource_params
      params.require(:resource).permit(:name, :filename, :content_type)
    end

    def extension(filename)
      extension = File.extname(filename)
      extension.tap { |e| e.slice!(0) }
    end

    def paginate(result)
      page = Integer(params[:page] || 1)
      count = Integer(params[:count] || 50).to_i
      offset = (page - 1) * count
      total = result.size
      @meta = {
        :pagination => {
          :total => total,
          :remaining => [total - offset - count, 0].max
        }
      }

      case result
      when Array
        result.slice(offset, count)
      else
        result.limit(count).offset(offset)
      end
    end

    def job_params
      params.require(:job).permit(:id, :state)
    end
  end
end