Docker = Meteor.require('dockerode');

var Convert = Meteor.require('ansi-to-html');
var convert = new Convert();

var DOCKER_HOST='192.168.59.103';
docker = new Docker({host: '192.168.59.103', port: '2375'});

hasDockerfile = function (directory) {
  return fs.existsSync(path.join(directory, 'Dockerfile'));
};

removeContainer = function (containerId, callback) {
  var container = docker.getContainer(containerId);
  container.kill(function (err) {
    if (err) { callback(err); return; }
    container.remove({v:1}, function (err) {
      if (err) { callback(err); return; }
      console.log('Deleted container: ' + containerId);
      callback(null);
    });
  });
};

removeContainerSync = function (containerId) {
  return Meteor._wrapAsync(removeContainer)(containerId);
};

deleteApp = function (app, callback) {
  if (!app.docker) {
    callback(null);
    return;
  }
  try {
    removeContainerSync(app.docker.Id);
  } catch (e) {
    console.error(e);
  }
  callback(null);
};

deleteAppSync = function (app) {
  return Meteor._wrapAsync(deleteApp)(app);
};

getContainerData = function (containerId, callback) {
  var container = docker.getContainer(containerId);
  container.inspect(function (err, data) {
    if (err) {
      callback(err, null);
      return;
    } else {
      data.Config.Volumes = convertVolumeObjToArray(data.Config.Volumes);
      data.Volumes = convertVolumeObjToArray(data.Volumes);
      data.VolumesRW = convertVolumeObjToArray(data.VolumesRW);
      callback(null, data);
      return;
    }
  });
};

getContainerDataSync = function (containerId) {
  return Meteor._wrapAsync(getContainerData)(containerId);
};

runContainer = function (app, image, callback) {
  var envParam = [];
  _.each(_.keys(app.config), function (key) {
    var builtStr = key + '=' + app.config[key];
    envParam.push(builtStr);
  });
  console.log(envParam);
  docker.createContainer({
    Image: image._id.toLowerCase(),
    Tty: false,
    Env: envParam,
    Hostname: app.name,
    name: app.name
  }, function (err, container) {
    if (err) { callback(err, null); return; }
    console.log('Created container: ' + container.id);
    // Bind volumes
    var binds = [];
    if (image.docker.Config.Volumes.length > 0) {
      _.each(image.docker.Config.Volumes, function (vol) {
        binds.push('/var/lib/docker/binds/' + app.name + vol.Path + ':' + vol.Path);
      });
    }
    // Start the container
    container.start({
      PublishAllPorts: true,
      Binds: binds
    }, function (err) {
      if (err) { callback(err, null); return; }
      console.log('Started container: ' + container.id);
      // Use dig to refresh the DNS
      exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {});
      callback(null, container);
    });
  });
};

runContainerSync = function (app, image) {
  return Meteor._wrapAsync(runContainer)(app, image);
};

restartContainer = function (containerId, callback) {
  var container = docker.getContainer(containerId);
  container.restart(function (err) {
    if (err) {
      console.log(err);
      callback(err);
      return;
    }
    console.log('Restarted container: ' + containerId);
    callback(null);
  });
};

restartContainerSync = function (containerId) {
  return Meteor._wrapAsync(restartContainer)(containerId);
};

var getFromImage = function (dockerfile) {
  var patternString = "(FROM)(.*)";
  var regex = new RegExp(patternString, "g");
  var fromInstruction = dockerfile.match(regex);
  if (fromInstruction && fromInstruction.length > 0) {
    return fromInstruction[0].replace('FROM', '').trim();
  } else {
    return null;
  }
};

restartApp = function (app, callback) {
  if (app.docker && app.docker.Id) {
    try {
      restartContainerSync(app.docker.Id);
    } catch (e) {
      console.error(e);
    }
    var containerData = getContainerDataSync(app.docker.Id);
    Fiber(function () {
      Apps.update(app._id, {$set: {
        status: 'READY',
        docker: containerData
      }});
    }).run();
    callback(null);
    // Use dig to refresh the DNS
    exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {});
  } else {
    callback(null);
  }
};

getAppLogs = function (app) {
  if (app.docker && app.docker.Id) {
    var container = docker.getContainer(app.docker.Id);
    container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) {
      if (err) { throw err; }
      Fiber(function () {
        Apps.update(app._id, {
          $set: {
            logs: []
          }
        });
      }).run();
      var logs = [];
      response.setEncoding('utf8');
      response.on('data', function (line) {
        logs.push(convert.toHtml(line.slice(8)));
        Fiber(function () {
          Apps.update(app._id, {
            $set: {
              logs: logs
            }
          });
        }).run();
      });
      response.on('end', function () {});
    });
  }
};

createTarFile = function (image, callback) {
  var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar');
  exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) {
    if (err) { callback(err, null); return; }
    console.log('Created tar file: ' + TAR_PATH);
    callback(null, TAR_PATH);
  });
};

createTarFileSync = function (image) {
  return Meteor._wrapAsync(createTarFile)(image);
};

var convertVolumeObjToArray = function (obj) {
  var result = [];
  if (obj !== null && typeof obj === 'object') {
    _.each(_.keys(obj), function (key) {
      var volumeObj = {};
      volumeObj.Path = key;
      volumeObj.Value = obj[key];
      result.push(volumeObj);
    });
  }
  return result;
};

getImageData = function (imageId, callback) {
  var image = docker.getImage(imageId.toLowerCase());
  image.inspect(function (err, data) {
    if (err) {
      callback(err, null);
      return;
    } else {
      data.Config.Volumes = convertVolumeObjToArray(data.Config.Volumes);
      data.ContainerConfig.Volumes = convertVolumeObjToArray(data.ContainerConfig.Volumes);
      callback(null, data);
      return;
    }
  });
};

getImageDataSync = function (imageId) {
  return Meteor._wrapAsync(getImageData)(imageId);
};

removeImage = function (imageId, callback) {
  var image = docker.getImage(imageId.toLowerCase());
  image.remove({force: true}, function (err) {
    if (err) { callback(err); return; }
    console.log('Deleted image: ' + imageId);
    callback(null);
  });
};

removeImageSync = function (imageId) {
  return Meteor._wrapAsync(removeImage)(imageId);
};

deleteImage = function (image, callback) {
  if (!image.docker) {
    callback(null, {});
    return;
  }
  try {
    removeImageSync(image.docker.Id);
  } catch (e) {
    console.error(e);
  }
  callback(null);
};

deleteImageSync = function (image) {
  return Meteor._wrapAsync(deleteImage)(image);
};

var defaultContainerOptions = function () {
  return [
    {
      Image: 'kite-dns',
      name: 'kite-dns',
      PortBindings: {'53/udp': [{ 'HostPort': '53', 'HostIp': '172.17.42.1' }]},
      Binds: ['/var/run/docker.sock:/tmp/docker.sock']
    }
  ];
};

checkDefaultImages = function (callback) {
  var defaultNames = defaultContainerOptions().map(function (container) {
    return container.name;
  });
  async.each(defaultNames, function (name, innerCallback) {
    var image = docker.getImage(name);
    image.inspect(function (err) {
      if (err) {
        if (err.reason === 'no such image') {
          innerCallback('no such image');
        } else {
          innerCallback(err);
        }
      } else {
        innerCallback();
      }
    });
  }, function (err) {
    if (err) {
      callback(err);
    } else {
      callback();
    }
  });
};

resolveDefaultImages = function () {
  var defaultNames = defaultContainerOptions().map(function (container) {
    return container.name;
  });
  async.each(defaultNames, function (name, innerCallback) {
    var image = docker.getImage(name);
    image.inspect(function (err) {
      if (err) {
        if (err.reason === 'no such image') {
          docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
            if (err) {
              innerCallback(err);
              return;
            } else {
              innerCallback();
            }
          });
        } else {
          innerCallback(err);
        }
      } else {
        innerCallback();
      }
    });
  });
};

checkDefaultContainers = function(callback) {
  var defaultNames = defaultContainerOptions().map(function (container) {
    return container.name;
  });
  async.each(defaultNames, function (name, innerCallback) {
    var container = docker.getContainer(name);
    container.inspect(function (err, data) {
      if (err) {
        innerCallback(err);
      } else {
        if (data && data.State && data.State.Running) {
          innerCallback(null);
        } else {
          innerCallback('Not running');
        }
      }
    });
  }, function (err) {
    if (err) {
      callback(err);
    } else {
      callback();
    }
  });
};

resolveDefaultContainers = function (callback) {
  var defaultNames = defaultContainerOptions().map(function (container) {
    return container.name;
  });
  killAndRemoveContainers(defaultNames, function (err) {
    if (err) {
      callback(err);
      return;
    }
    upContainers(defaultContainerOptions(), function (err) {
      callback(err);
    });
  });
};

reloadDefaultContainers = function (callback) {
  console.log('Reloading default containers.');

  var defaultNames = defaultContainerOptions().map(function (container) {
    return container.name;
  });

  var ready = false;
  async.until(function () {
    return ready;
  }, function (callback) {
    docker.listContainers(function (err, containers) {
      if (!err) {
        ready = true;
      }
      callback();
    });
  }, function (err) {
    console.log(err);
    console.log('Removing old Kitematic default containers.');
    killAndRemoveContainers(defaultNames, function (err) {
      console.log('Removed old Kitematic default containers.');
      if (err) {
        console.log('Removing old Kitematic default containers ERROR.');
        callback(err);
        return;
      }
      console.log('Loading new Kitematic default images.');
      docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
        if (err) {
          callback(err);
          return;
        }
        console.log('Starting new Kitematic default containers.');
        upContainers(defaultContainerOptions(), function (err) {
          callback(err);
        });
      });
    });
  });
};

upContainers = function (optionsList, callback) {
   var createDefaultContainer = function (options, innerCallback) {
    docker.createContainer(options, function (err, container) {
      if (err) {
        innerCallback(err);
        return;
      }
      container.start({
        PublishAllPorts: true,
        PortBindings: options.PortBindings,
        Binds: options.Binds
      }, function (err) {
        innerCallback(err);
      });
    });
  };

  async.each(optionsList, function (options, innerCallback) {
    var container = docker.getContainer(options.name);
    container.inspect(function (err, data) {
      if (err) {
        if (err.reason.indexOf('no such container') !== -1) {
          createDefaultContainer(options, function (err) {
            innerCallback(err);
          });
        } else {
          innerCallback(err);
        }
      } else {
        if (data && !data.State.Running) {
          container.start(function (err) {
            innerCallback(err);
          });
        } else {
          innerCallback();
        }
      }
    });
  }, function (err) {
    callback(err);
  });
};

removeImages = function (names, callback) {
  async.each(names, function (name, innerCallback) {
    var image = docker.getImage(name);
    image.remove(function (err) {
      if (err) {
        console.log('remove image error');
        console.log(err);
        if (err.reason === 'no such image') {
          innerCallback();
        } else {
          innerCallback(err);
        }
      } else {
        innerCallback();
      }
    });
  }, function (err) {
    callback(err);
  });
};

killAndRemoveContainers = function (names, callback) {
  async.each(names, function (name, innerCallback) {
    var container = docker.getContainer(name);
    container.inspect(function (err, data) {
      if (err) {
        innerCallback();
        return;
      }
      if (data.State.Running) {
        // Kill it
        container.kill(function (err) {
          if (err) {
            innerCallback(err);
          } else {
            // Remove it
            container.remove(function (err) {
              innerCallback(err);
            });
          }
        });
      } else {
        container.remove(function (err) {
          innerCallback(err);
        });
      }
    });
  }, function (err) {
    callback(err);
  });
};

pullImageFromDockerfile = function (dockerfile, imageId, callback) {
  var fromImage = getFromImage(dockerfile);
  console.log('From image: ' + fromImage);
  var installedImage = null;
  try {
    installedImage = getImageDataSync(fromImage);
  } catch (e) {
    console.error(e);
  }
  if (fromImage && !installedImage) {
    Fiber(function () {
      Images.update(imageId, {
        $set: {
          buildLogs: []
        }
      });
    }).run();
    var logs = [];
    docker.pull(fromImage, function (err, response) {
      if (err) { callback(err); return; }
      response.setEncoding('utf8');
      response.on('data', function (data) {
        try {
          var logData = JSON.parse(data);
          var logDisplay = '';
          if (logData.id) {
            logDisplay += logData.id + ' | ';
          }
          logDisplay += logData.status;
          if (logData.progressDetail && logData.progressDetail.current && logData.progressDetail.total) {
            logDisplay += ' - ' + Math.round(logData.progressDetail.current / logData.progressDetail.total * 100) + '%';
          }
          logs.push(logDisplay);
          Fiber(function () {
            Images.update(imageId, {
              $set: {
                buildLogs: logs
              }
            });
          }).run();
        } catch (e) {
          console.error(e);
        }
      });
      response.on('end', function () {
        console.log('Finished pulling image: ' + fromImage);
        callback(null);
      });
    });
  } else {
    callback(null);
  }
};

buildImage = function (image, callback) {
  Fiber(function () {
    var tarFilePath = createTarFileSync(image);
    Images.update(image._id, {
      $set: {
        buildLogs: []
      }
    });
    docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) {
      if (err) { callback(err); }
      console.log('Building Docker image...');
      var logs = [];
      response.setEncoding('utf8');
      response.on('data', function (data) {
        try {
          var line = JSON.parse(data).stream;
          logs.push(convert.toHtml(line));
          Fiber(function () {
            Images.update(image._id, {
              $set: {
                buildLogs: logs
              }
            });
          }).run();
        } catch (e) {
          console.error(e);
        }
      });
      response.on('end', function () {
        console.log('Finished building Docker image.');
        try {
          fs.unlinkSync(tarFilePath);
          console.log('Cleaned up tar file.');
        } catch (e) {
          console.error(e);
        }
        Fiber(function () {
          var imageData = null;
          try {
            imageData = getImageDataSync(image._id);
            Images.update(image._id, {
              $set: {
                docker: imageData,
                status: 'READY'
              }
            });
          } catch (e) {
            console.log(e);
            Images.update(image._id, {
              $set: {
                status: 'ERROR'
              }
            });
          }
          var oldImageId = null;
          if (image.docker && image.docker.Id) {
            oldImageId = image.docker.Id;
          }
          if (oldImageId && imageData && oldImageId !== imageData.Id) {
            try {
              removeImageSync(oldImageId);
            } catch (e) {
              console.error(e);
            }
          }
        }).run();
        callback(null);
      });
    });
  }).run();
};

Meteor.methods({
  runApp: function (app) {
    this.unblock();
    var image = Images.findOne({_id: app.imageId});
    try {
      removeContainerSync(app.name);
    } catch (e) {}
    try {
      var container = runContainerSync(app, image);
      var containerData = getContainerDataSync(container.id);
      Meteor.setTimeout(function () {
        Apps.update(app._id, {$set: {
          docker: containerData,
          status: 'READY'
        }});
      }, 2500);
    } catch (e) {
      console.error(e);
    }
  },
  getDockerHost: function () {
    return DOCKER_HOST;
  },
  reloadDefaultContainers: function () {
    return Meteor._wrapAsync(reloadDefaultContainers)();
  },
  checkDefaultImages: function () {
    return Meteor._wrapAsync(checkDefaultImages)();
  },
  resolveDefaultImages: function () {
    return Meteor._wrapAsync(resolveDefaultImages)();
  },
  checkDefaultContainers: function () {
    return Meteor._wrapAsync(checkDefaultContainers)();
  },
  resolveDefaultContainers: function () {
    return Meteor._wrapAsync(resolveDefaultContainers)();
  }
});