;
/*************************************************************
 *
 * Copyright (c) 2023 ysrock Co., Ltd.	<info@ysrock.co.jp>
 * Copyright (c) 2023 Yasuo Sugano	<sugano@ysrock.co.jp>
 *
 * Version	: 2.0.2
 * Update	  : 2023.01.26
 *
 ************************************************************
 *
 *	使い方
 *		$(INPUT_FILE).split_uploader(PHP_PATH, CALLBACK, OPTION={});
 *
 *	パラメーター
 *		INPUT_FILE (HTMLInputElement.files)
 *			e.g.) $('#file')
 *			e.g.) $('#files > input.file')
 *
 *		PHP_PATH (String)
 *			uploader.phpのパス
 *				e.g.) https://www.hogehoge.com/uploader.php
 *				e.g.) ./uploader.php
 *
 *		CALLBACK (Callback)
 *			アップロードが完了した時に実行されるコールバック関数
 *				第一引数 (String)
 *					保存されたファイルパス
 *				第二引数 (Object)
 *					'uuid' (string)
 *						UUID
 *					'triggerElement' (Element)
 *						トリガーとなったエレメント
 *					'file' (File)
 *						Fileオブジェクト
 *					'result' (string)
 *						base64
 *					'splitSize' (number)
 *						分割するバイト数
 *					'counter' (number)
 *						分割で送信した回数
 *					'denominator' (number)
 *						分割で送信する回数
 *
 *		OPTION (Object)
 *			dndElement (jQuery DOM Element)
 *				このエレメント内にドロップされたファイルを読み込む
 *			splitSize (Number) = 1048576
 *				分割するファイルサイズ
 *			mimeAllow (Array)
 *				アップロードを許可するMIME
 *					e.g.) ['image/jpeg', 'image/png']
 *			mimeFail (Callback)
 *				許可されなかった場合のコールバック関数
 *				第一引数：許可されなかったFILE (File)
 *			cbReadPrepare (Callback)
 *				ファイルを読み込む直前に実行されるコールバック関数
 *				第一引数 (Object)
 *					'uuid' (string)
 *						UUID
 *					'triggerElement' (Element)
 *						トリガーとなったエレメント
 *			cbReadDone (Callback)
 *				ファイルが読み込まれた時に実行されるコールバック関数
 *				第一引数 (Object)
 *					'uuid' (string)
 *						UUID
 *					'triggerElement' (Element)
 *						トリガーとなったエレメント
 *					'file' (File)
 *						Fileオブジェクト
 *					'result' (string)
 *						base64
 *			cbNext (Callback)
 *				分割ファイルがアップロードされる度に実行されるコールバック関数
 *				第一引数 (Object)
 *					'uuid' (string)
 *						UUID
 *					'triggerElement' (Element)
 *						トリガーとなったエレメント
 *					'file' (File)
 *						Fileオブジェクト
 *					'result' (string)
 *						base64
 *					'splitSize' (number)
 *						分割するバイト数
 *					'counter' (number)
 *						分割で送信した回数
 *					'denominator' (number)
 *						分割で送信する回数
 *				第二引数 (Object)
 *					jqXHR
 *			cbFail (Callback)
 *				アップロードが失敗した時に実行されるコールバック関数
 *				第一引数 (Object)
 *					'uuid' (string)
 *						UUID
 *					'triggerElement' (Element)
 *						トリガーとなったエレメント
 *					'file' (File)
 *						Fileオブジェクト
 *					'result' (string)
 *						base64
 *					'splitSize' (number)
 *						分割するバイト数
 *					'counter' (number)
 *						分割で送信した回数
 *					'denominator' (number)
 *						分割で送信する回数
 *				第二引数 (Object)
 *					jqXHR
 *
 ************************************************************/
'use strict';
(function($){
  $.fn.split_uploader = function(php_path, callback_done, opts){
    if(opts === undefined) opts = {};
    // 分割するファイルサイズ
    if(typeof opts.splitSize != "number") opts.splitSize = 1048576;
    // 参照ボタンからファイルが選択された
    $(this).change( funcInputFile() );
    // ドロップされたファイルを読み込む
    if('dndElement' in opts) dragAndDrop(opts.dndElement);





    /************************************************************
     *
     * 参照ボタンからファイルが選択された
     *
     ************************************************************/
    function funcInputFile(){
      return(function(){
        // 未選択の場合は処理しない
        if(!$(this).val()) return;
        // ファイルを読み込む
        readFile($(this)[0], this);
        // 未選択状態に戻す
        $(this).val('');
      });
    };
    /********** END 参照ボタンからファイルが選択された ********************/



    /************************************************************
     *
     * ドロップされたファイルを読み込む
     *
     ************************************************************/
    function dragAndDrop(elem){
      $(document).on('drop', elem, function(e){
        e.stopPropagation();
        e.preventDefault();
        // エレメント違いは処理しない
        let isHit = false;
        for (let i=0, len=elem.length; i<len; i++) {
          if (elem[i] !== e.target) continue;
          isHit = true;
          break;
        };
        if (!isHit) return;
        // ファイルを読み込む
        readFile(e.originalEvent.dataTransfer, e.target);
      })
      .on('dragover', elem, function(e){
        e.stopPropagation();
        e.preventDefault();
      })
      .on('dragleave', elem, function(e){
        e.stopPropagation();
        e.preventDefault();
      });
    };
    /********** END ドロップされたファイルを読み込む ********************/



    /************************************************************
     *
     * ファイルを読み込む
     *
     ************************************************************/
    function readFile(fileElement, triggerElement){
      let files = fileElement.files;
      // ファイルをひとつずつ処理する
      for(let i=0, len=files.length; i<len; i++){
        // 取得するファイル
        let file = files[i];
        // MIME制限にかかった場合は処理しない
        if(!allowMIME(file)) continue;
        // UUIDを作成
        let uuid = getUUID();
        // 分割回数の計算
        let denominator = Math.ceil(file.size / opts.splitSize) - 1;
        // チャンク
        let chunks = {
          'uuid' : uuid
         ,'file' : file
         ,'triggerElement' : triggerElement
         ,'splitSize' : opts.splitSize
         ,'denominator' : denominator
        };

        // コールバック：ファイルを読み込む直前
        if(typeof opts.cbReadPrepare == "function") opts.cbReadPrepare( chunks );

        // FileReader
        let FR = new FileReader();
        FR.addEventListener('load', function(e){
          // 引数を引き継ぐ
          let chunks = e.target.chunks;
          // 全体データをbase64で取得
          chunks.base64 = e.target.result;
          // コールバック：ファイルを読み込んだ直後
          if(typeof opts.cbReadDone == "function") opts.cbReadDone( chunks );
          // ファイルを分割してアップロード
          chunks.counter = 0;
          splitFile( chunks );
        });
        // 引数を引き継ぐ
        FR.chunks = chunks;
        FR.readAsDataURL(file);

      };// END for ファイルをひとつずつ処理する
    };
    /********** END ファイルを読み込む ********************/



    /************************************************************
     *
     * ファイルを分割する
     *
     ************************************************************/
    function splitFile( chunks ){
      // 分割開始位置
      let begin = Number(chunks.counter) * Number(chunks.splitSize);
      // 分割終了位置
      let end = Number(begin) + Number(chunks.splitSize);

      // FileReader
      let FR = new FileReader();
      FR.onload = function(e){
        // 引数を引き継ぐ
        let chunks = e.target.chunks;
        // 分割データ
        chunks.data = e.target.result;
        // 非同期でアップロードする
        ajaxUpload(chunks);
      };
      FR.chunks = chunks;
      FR.readAsArrayBuffer(chunks['file'].slice(begin, end));
    };
    /********** END ファイルを分割する ********************/



    /************************************************************
     *
     * 非同期でアップロードする
     *
     ************************************************************/
    function ajaxUpload( chunks ){
      $.ajax({
        'url' : php_path
       ,'type' : 'post'
       ,'processData' : false
       ,'contentType' : "application/octet-stream"
       ,'data' : chunks.data
       ,'headers' : {
          'chunk-uuid' : chunks.uuid
         ,'chunk-counter' : chunks.counter
         ,'chunk-denominator' : chunks.denominator
        }
      })
      .done(function(result){
        // 続けてアップロード
        if('next' in result) nextChunk(chunks, result);
        // 完了
        else if(typeof callback_done == "function") callback_done(result.file, chunks);
      })
      .fail(function(result){
        // アップロードが失敗した時に実行されるコールバック関数
        if(typeof opts.cbFail == "function") opts.cbFail(chunks, result);
      })
      .always(function(result){
      });
    };
    /********** END 非同期でアップロードする ********************/



    /************************************************************
     *
     * 続けてアップロード
     *
     ************************************************************/
    function nextChunk(chunks, result){
      // 分割ファイルがアップロードされる度に実行されるコールバック関数
      if(typeof opts.cbNext == "function") opts.cbNext(chunks, result);
      // 次の番号を指定
      chunks.counter = result.next;
      // ファイルを分割してアップロード
      splitFile( chunks );
    };
    /********** END 続けてアップロード ********************/



    /************************************************************
     *
     * MIME制限にかかった場合はfalseを返す
     *
     ************************************************************/
    function allowMIME(file){
      // MIME制限がない場合は許可
      if(typeof opts != "object" || opts.mimeAllow === undefined) return true;
      // 文字列の場合は配列に変換する
      if(typeof opts.mimeAllow == "string") opts.mimeAllow = [ opts.mimeAllow ];
      // 一つでも一致すれば許可する
      for(let i=0, len=opts.mimeAllow.length; i<len; i++){
        // 一致すればtrueを返す
        if(file.type == opts.mimeAllow[i]) return true;
      };// END for 一つでも一致すれば許可する
      // コールバックがある場合実行する
      if(typeof opts.mimeFail == "function") opts.mimeFail( file );
      // 拒否
      return false;
    };
    /********** END MIME制限にかかった場合はfalseを返す ********************/



    /************************************************************
     *
     * UUIDを取得する
     *
     ************************************************************/
    function getUUID(){
      return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c){
        let r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
        return v.toString(16);
      });
    };
    /********** END UUIDを取得する ********************/


  };
})(jQuery);