/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
// Removed import
import fs from "fs";
import path from 'path';
import GitHubApi from 'github';
import parseDiff from 'parse-diff';

// check if pr submitted to master branch
console.log("checkMasterBranch")
const isMergeRefMaster = danger.github.pr.base.ref === 'master';
if(!isMergeRefMaster){
  warn("You'd better to submit PR to master branch as the development of Weex is on master branch.");
}

// match regex line by line
function matchRegex(pr_body,regex){
  const lines = pr_body.split("\n");
  for (let i = 0; i < lines.length; i++) {
    if(lines[i].match(regex)){
      return true;
    }
  }
  return false;
}

var pr_body = danger.github.pr.body.toLowerCase();
// Because Pr description template include the following line：
// 1. Write the corresponding [documentation](https://github.com/apache/incubator-weex/blob/master/CONTRIBUTING.md#contribute-code-or-document)
// so we should check the documentation below the ### checklist
console.log("checkDocumentation");
const index = pr_body.indexOf("checklist")
const includeChecklist = (index!=-1)
if(includeChecklist && !matchRegex(pr_body.substring(index),/documentation.*http/)){
  const msg = "If you update the code, "+
    "maybe you should update the documentation and add the documentation link in the PR description. \n" +
    "here is the guide about how to contribute documentation: <a href='https://github.com/apache/incubator-weex/blob/master/CONTRIBUTING.md#contribute-code-or-document'>https://github.com/apache/incubator-weex/blob/master/CONTRIBUTING.md#contribute-code-or-document</a>";
  warn(msg);
}

// check if pr contains a demo link
console.log("checkDemo");
if(!matchRegex(pr_body,/demo.*http/)){
  const msg =  "If your PR is about fixing a bug excluding crash the code,"+
    "you should add the demo link in the PR description. "+
    "Demo link: <a href='http://dotwe.org/vue'>http://dotwe.org/vue</a>";
  warn(msg);
}

// check if pr bind the github milestone
console.log("checkMileStone");
if(!danger.github.pr.milestone){
  warn("Current pr not bind the milestone");
}

// Make sure there are changelog entries
const hasChangelog = danger.git.modified_files.includes("CHANGELOG.md")
if (!hasChangelog) { 
  warn(`No Changelog changes! - <i>Can you add a Changelog? To do so,append your changes to the changelog.md</i>`);
}

const jsFiles = danger.git.created_files.filter(path => path.endsWith("js"));

function absolute (relPath) {
  return path.resolve(__dirname, relPath)
}

const flowIgnorePaths = [
  'node_modules',
  'test',
  'build',
  'examples',
  'android',
  'ios',
  'bin',
  'dist',
  'flow-typed'
].map(function (rel) {
  return absolute(rel)
});

// new js files should have `@flow` at the top
const unFlowedFiles = jsFiles.filter(filepath => {
  let i = 0
  const len = flowIgnorePaths.length
  while (i < len) {
    const p = flowIgnorePaths[i]
    if (absolute(filepath).indexOf(p) > -1) {
      // ignore this file because it's in the flow-ignore-paths.
      return false;
    }
    i++
  }
  const content = fs.readFileSync(filepath);
  return !content.includes("@flow");
});

if (unFlowedFiles.length > 0) {
  warn(
    `These new JS files do not have Flow enabled: ${unFlowedFiles.join(", ")}`
  );
}

// Error or Warn when delete public interface
var isNotDanger = false;
console.log('pr.title:'+danger.github.pr.title)
if(!isNotDanger && danger.github.pr.title
  && danger.github.pr.title.match(/@notdanger/i)){
  isNotDanger = true;
}
console.log('pr.body:'+danger.github.pr.body)
if(!isNotDanger && danger.github.pr.body
  && danger.github.pr.body.match(/@notdanger/i)){
  isNotDanger = true;
}
if(!isNotDanger){
  for (let c of danger.git.commits) {
    // console.log("msg:" + c.message);
    if (c.message && c.message.match(/@notdanger/i)) {
      isNotDanger = true;
      break;
    }
  }
}

// File name match any of these patterns will be ignored.
function is_ignored_public_check(file) {
  var ignored_break_change_pattern = [
    /^android\/sdk\/src\/test\/.+/,
     /^android\/playground\/.+/
  ];
  for (let p of ignored_break_change_pattern) {
    if (file.match(p)) {
      return true;
    }
  }
  return false;
}

async function checkBreakChange(file){
  var diff = await danger.git.diffForFile(file);
  if (diff && diff.removed && diff.removed.match(/^-\s*?public\s+[\s\S]+$/gm)) {
    if (isNotDanger) {
      warn("Potential BREAK CHANGE. Modify public in   " + file);
    } else {
      warn(
        "Potential BREAK CHANGE. Modify public in " +
          file +
          " without metion it in commit message. You'd better add '@notdanger' in your commit log. "
      );
    }
  }
}

var has_sdk_changes = false;
var has_test_changes = false;
var filesToVerifySrcHeader = [];
var fileCount = 0;

const type_unknown = 0;
const type_ios_sdk = 1;
const type_android_sdk = 2;
const type_ios_test = 3;
const type_android_test = 4;
const type_jsfm = 5;
const type_jsfm_test = 6;
const type_ui_test = 8;

const getFileType = file => {
  if (file.match(/WeexSDK\/Sources\/.+\.(m|h|mm)/)) {
    return type_ios_sdk;
  } else if (file.match(/WeexSDKTests\//)) {
    return type_ios_test;
  } else if (file.match(/android\/sdk\/src\/test\/.+\.java/)) {
    return type_android_test;
  } else if (file.match(/android\/sdk\/src\/main\/java\/.+\.java/)) {
    return type_android_sdk;
  } else if (
    file.match(/runtime\/.+\.js/)
  ) {
    return type_jsfm;
  } else if (file.match(/test\/js-framework\/.+\.js/)) {
    return type_jsfm_test;
  } else if(file.match(/test\/scripts\/.+\.js/) || file.match(/test\/pages\/.+\.vue/)){
    return type_ui_test
  }else{
    return type_unknown
  }
}

const checkChangedFile = file => {
  fileCount++;
  console.log("check changed file:"+file)
  let fileType = getFileType(file)

  has_sdk_changes =
    has_sdk_changes ||
    fileType == type_android_sdk ||
    fileType == type_ios_sdk ||
    fileType == type_jsfm;
  has_test_changes =
    has_test_changes ||
    fileType == type_android_test ||
    fileType == type_ios_test ||
    fileType == type_jsfm_test ||
    fileType == type_ui_test

};

const checkAndroidBreakChange = file => {
  if (getFileType(file) == type_android_sdk && !is_ignored_public_check(file)) {
    schedule(async () => {
      await checkBreakChange(file);
    });
  }
}

const checkFileToVerifySrcHeader = file => {
  if (
    file.endsWith(".h") ||
    file.endsWith(".m") ||
    file.endsWith(".mm") ||
    file.endsWith(".java") ||
    file.endsWith(".js")
  ) {
    filesToVerifySrcHeader.push(file);
  }
};

if (danger.git.modified_files) {
  danger.git.modified_files.forEach(file => {
    checkChangedFile(file);
    checkAndroidBreakChange(file);
    checkFileToVerifySrcHeader(file);
  });
}

console.log('checkFileToVerifySrcHeader')
if (danger.git.created_files) {
  danger.git.created_files.forEach(file => {
    checkChangedFile(file);
    checkFileToVerifySrcHeader(file);
  });
}
console.log('checkAndroidBreakChange')
if (danger.git.deleted_files) {
  danger.git.deleted_files.forEach(file => {
    checkChangedFile(file);
    checkAndroidBreakChange(file);
  });
}

if (has_sdk_changes && !has_test_changes) {
  if(isNotDanger) warn("This PR modify SDK code without add/modify testcases.")
  // else fail("This PR modify SDK code. Please add/modify corresponding testcases. If it is ok, please comment about it. Or put '@notdanger' in you commit message.");
}

//check ios copyright
//see scripts/rh/header.template
const copyright_header_components = [
  "Licensed to the Apache Software Foundation \\(ASF\\) under one",
  "or more contributor license agreements.  See the NOTICE file",
  "distributed with this work for additional information",
  "regarding copyright ownership\\.  The ASF licenses this file",
  'to you under the Apache License, Version 2\\.0 \\(the',
  '"License"\\); you may not use this file except in compliance',
  'with the License\\.  You may obtain a copy of the License at'
];

//path prefix
const ignoreCopyrightVerifyPath = [
  'test',
  'packages',
  'pre-build',
  'runtime/frameworks/legacy/core',
  'test/js\-framework/case',
  'android/playground/app/src/main/assets',
  'android/sdk/assets',
  'android/sdk/src/main/java/org/apache/weex/utils/WXDataStructureUtil.java',
  'ios/playground/bundlejs',
  'ios/sdk/WeexSDK/Resources',
  'ios/sdk/WeexSDK/Sources/Layout',
  'ios/sdk/WeexSDK/dependency/SRWebSocket',
  'weex_core/Source/include/JavaScriptCore',
  'weex_core/Source/include/wtf',
  'weex_core/Source/base/third_party/icu',
  'weex_core/Source/base/base64/modp_base64'
]

console.log('copyright_header_components')
filesToVerifySrcHeader.forEach(filepath => {
  for(var i=ignoreCopyrightVerifyPath.length-1;i>=0;i--){
    if(filepath.startsWith(ignoreCopyrightVerifyPath[i])){
      return
    }
  }
  const content = fs.readFileSync(filepath).toString();
  for (const line of copyright_header_components) {
    if (!content.match(new RegExp(line))) {
      console.error("Code file "+ filepath +" does not have the copyright header.");
      fail("Code file "+ filepath +" does not have the copyright header.");
      return;
    }
  }

  // check cn for source code
  var reg = /[\u4e00-\u9FA5]+/;
  var res = reg.test(content);
  if(res){
    console.error("Code file "+ filepath +" has cn source code.");
    console.error("position " + reg.exec(content));
    fail("Code file "+ filepath +" has cn source code.");
    return ;
  }
});


/*
 * try to find the appropriate reviewer according to the blame info
 * will be seperated to a danger plugin
 */

// console.log('findReviewer')
// schedule(new Promise((resolve, reject) => {
//   try {
//     findReviewer(resolve, reject)
//   } catch (e) {
//     console.log(e)
//     resolve()
//   }
// }));

function findReviewer(resolve, reject) {
  var github = new GitHubApi({
    protocol: "https",
    host: "api.github.com",
  });

  var fileToDeletedLinesMap = {}
  var fileToNormalLinesMap = {}
  var fileToBlamesMap = {}
  var repoName = danger.github.pr.base.repo && danger.github.pr.base.repo.name
  github.pullRequests.get({
    owner: danger.github.pr.base.user.login,
    repo: repoName,
    number: danger.github.pr.number,
    headers: {Accept: 'application/vnd.github.diff',"user-agent": "node.js"}
  }, function (err, result) {
    if ("undefined" === typeof result || "undefined" === typeof result.data || err) {
      console.log('result:'+result+', error:'+err);
      resolve()
      return
    }
    console.log('result:'+result);
    parseDeleteAndNormalLines(result.data, fileToDeletedLinesMap, fileToNormalLinesMap)
    console.log('getContent')
    var promises = danger.git.modified_files.map(function(file) {
      let repoURL = danger.github.pr.base.repo.html_url
      let fileName = file.replace(/^.*[\\\/]/, '')
      let blameURL = repoURL + '/blame/' + danger.github.pr.base.ref + '/' + file
      // console.log("Getting blame html: " + blameURL)
      console.log('getContent2')
      return getContent(blameURL)
    });

    console.log('findBlameReviewers')
    Promise.all(promises).then(datas => {
      datas.forEach(function(data, index) {
        fileToBlamesMap[danger.git.modified_files[index]] = parseBlame(data);
      });
      findBlameReviewers(fileToDeletedLinesMap, fileToNormalLinesMap, fileToBlamesMap)
      resolve()
    })
  });
}

function getContent(url) {
  // return new pending promise
  return new Promise((resolve, reject) => {
    // select http or https module, depending on reqested url
    const lib = url.startsWith('https') ? require('https') : require('http');
    const request = lib.get(url, (function (url) {
      return (response) => {
        // handle http errors
        console.log('response:', response.statusCode)
        if (response.statusCode < 200 || response.statusCode > 299) {
          if (response.statusCode === 404  || response.statusCode === 502) {
            // ignore this, probably a renamed file,or .so that can't blame
            return resolve('')
          }
          reject(new Error('Failed to load page, status code: ' + response.statusCode + ', '
            + ' url: ' + url));
        }
        // temporary data holder
        const body = [];
        // on every content chunk, push it to the data array
        response.on('data', (chunk) => body.push(chunk));
        // we are done, resolve promise with those joined chunks
        response.on('end', () => resolve(body.join('')));
      }
    })(url));
    // handle connection errors of the request
    request.on('error', (err) => reject(err))
    })
}

function parseDeleteAndNormalLines(diffData, fileToDeletedLinesMap, fileToNormalLinesMap) {
  try {
    console.log('parseDeleteAndNormalLines')
    var diffs = parseDiff(diffData)
    console.log('diffs:'+diffs)
    if(diffs&&diffs instanceof Array){
      diffs.forEach(diff => {
        fileToDeletedLinesMap[diff.from] = [];
        fileToNormalLinesMap[diff.from] = [];
        if(diff&&diff.chunks&&diff.chunks instanceof Array){
          diff.chunks.forEach(chunk => {
            if(chunk&&chunk.changes&&chunk.changes instanceof Array){
              chunk.changes.forEach(change => {
                if (change&&change.del) {
                  fileToDeletedLinesMap[diff.from].push(change.ln)
                }
                if (change&&change.normal) {
                  fileToNormalLinesMap[diff.from].push(change.ln1)
                }
              })
            }
          })
        }
      })
    }
  } catch (error) {
    console.log(error)
  }

}


function parseBlame(html) {
  let regular = /(<img alt="@([^"]+)" class="avatar blame-commit-avatar"|<tr class="blame-line")/g
  var match
  var currentUser
  var lines = []
  while ((match = regular.exec(html)) !== null) {
    let user = match[2]
    if (user) {
      currentUser = user
    } else {
      lines.push(currentUser)
    }
  }

  return lines
}

function findBlameReviewers(fileToDeletedLinesMap, fileToNormalLinesMap, fileToBlamesMap) {
  var reviewers = {};

  // deleted lines get 3 points, normal line get 1 point
  Object.keys(fileToDeletedLinesMap).forEach(function (file) {
    let deletedLines = fileToDeletedLinesMap[file]
    var blames = fileToBlamesMap[file]
    if (!blames) {
      console.error(`failed to find blame info for (${file})`)
      return;
    }
    deletedLines.forEach(lineNumber => {
      var name = blames[lineNumber]
      if (name && !!reviewers) {
        reviewers[name] = (reviewers[name] || 0) + 3
      }
    })
  });

  Object.keys(fileToNormalLinesMap).forEach(function (file) {
    let normalLines = fileToNormalLinesMap[file];
    var blames = fileToBlamesMap[file]
    if (!blames) {
      console.error(`failed to find blame info for (${file})`)
      return;
    }
    normalLines.forEach(lineNumber => {
      var name = blames[lineNumber]
      if (name && !!reviewers) {
        reviewers[name] = (reviewers[name] || 0) + 1
      }
    })
  });

  console.log('blame point:', reviewers)
  var names = Object.keys(reviewers)
  if(!names||!names instanceof Array||names.length<=0)return;
  names.sort((name1, name2) => {
    return reviewers[name1] > reviewers[name2] ? -1 : 1
  })

  var prUser = danger.github.pr.user.login
  names.splice(names.findIndex(el => {
    return el === prUser
  }), 1)

  if (names.length > 0) {
    if (names.length > 2) {
      names = names.slice(0, 2)
    }
    names = names.map(name => {
      return '@' + name
    })

    message("According to the blame info, we recommended " + names.join(' , ') + " to be the reviewers.")
  }
}
/*
 * find reviewer end
 */
