/*
CreateCalendar.js
A script to generate a complete year calender baased on Master-Page elemnents in InDesign
Version: 1.2, 01-11-2024
Add: 
  - day_symbol
  - date_julian
  - month_julian
  - year_julian
  - year_2_julian
  - mini_month

https://sadeea.de/generate-calendar-indesign

Licensed 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.

Credit: Mohammad Sadeea
*/

#target indesign
#targetengine "Grrrr"

// If you need, you can change these arrays
var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
var months_f = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"];
var months_a = ["كَانُون ٱلثَّانِي","شُبَاط","آذَار","نَيْسَان","أَيَّار","حَزِيرَان","تَمُّوز","آب","أَيْلُول","تِشْرِين ٱلْأَوَّل","تِشْرِين ٱلثَّانِي","كَانُون ٱلْأَوَّل",];
var months_a2 = ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"];
var months_a3 = ["جانفي", "فيفري", "مارس", "أفريل", "ماي", "جوان", "جويلية", "أوت", "سبتمبر", "أكتوبر","نوفمبر", "ديسمبر"];
var days = ["Sunday",	"Monday",	"Tuesday",	"Wednesday",	"Thursday",	"Friday", "Saturday"];
var days_symbol = ["uF02B",	"uF02C",	"uF02D",	"uF02E",	"uF02F",	"uF030", "uF02A"];
var days_f = ["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"];
var days_a = ["الأحد","الأثنين","الثلاثاء","الأربعاء","الخميس","الجمعة", "السبت"];
var minMonth_daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var hijri_month = ["Muharram","Safar","Rabi al-Awwal","Rabi al-Thani","Jumada al-Awwal","Jumada al-Thani","Rajab","Shaban","Ramadan","Shawwal","Dhu al-Qadah","Dhu al-Hijjah"];
var hijri_month_a = ["محرم", "صفر", "ربيع الأول", "ربيع الثاني", "جمادى الأول", "جمادى الثانية", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة"];
var document;

// Change ths variable to the desired starting day (0 = Sunday, 1 = Monday, etc.)
var firstDayOfTheWeekForMiniMonth = 1;

var CalendarVariables = {
  "date" : "Tha date of the day (ex. 16)",
  "date_julian" : "Tha Julian date of the day (ex. 3)",
  "day" : "The name of the day in english (ex Monday)",
  "day_a" : "The name of the day in arabic (ex الأثنين)",
  "day_f" : "The name of the day in french (ex Lundi)",
  "day_symbol" : "The name of the day as a unicode as in 'KFGQPC Arabic Symbols 01' font",
  "month" : "The name of the month in english (ex January)",
  "month_a" : "The name of the month in arabic (ex كَانُون ٱلثَّانِي)",
  "month_f" : "The name of the month in french (ex Janvier)",
  "month_a2" : "The name of the month in arabic (ex يناير)",
  "month_a3" : "The name of the month in arabic (ex جانفي)",
  "month_julian" : "The name of the month (Julian year) in english",
  "year" : "The 4 digits of the year",
  "year_2" : "The 2 digits of the year",
  "year_julian" : "The 4 digits of the Julian year",
  "year_2_julian" : "The 2 digits of the Julian year",
  "hijri_date" : "Tha Hijri date of the day (ex. 16)",
  "hijri_month" : "The name of the Hijri month in english (ex Muharram)",
  "hijri_month_a" : "The name of the Hijri month in arabic (ex محرم)",
  "hijri_year" : "The 4 digits of the Hijri year",
  "days_gone" : "The number of days that have passed in the year(ex 126)",
  "days_left" : "The number of days left in the year (ex 239)",
  "week" : "The number of the week (ex 11)",
  "mini_month" : "the entire month as a table"
};

function createProgressPanel() {
  if (app.documents.length == 0) {
    alert('Please open a file first!');
    return;
  }
  
  document = app.activeDocument;
  
  if (!(document instanceof Document)) {
    alert('Please open a file first!');
    return;
  }

  win = Window.find('palette', 'Generate Calendar');
  if (win == null) {
    win = new Window('palette', 'Generate Calendar');
    win.text = "Generate Calendar";
    win.orientation = "column";
    win.alignChildren = ["center", "top"];
    win.spacing = 20;
    win.margins = 20;

    var tpanel1 = win.add("tabbedpanel", undefined, undefined, { name: "tpanel1" });
    tpanel1.alignChildren = "fill";
    tpanel1.preferredSize.width = 300;
    tpanel1.margins = 0;

    var tab1 = tpanel1.add("tab", undefined, undefined, { name: "tab1" });
    tab1.text = "Options";
    tab1.orientation = "column";
    tab1.alignChildren = ["left", "top"];
    tab1.spacing = 10;
    tab1.margins = 10;

    var group1 = tab1.add("group", undefined, { name: "group1" });
    group1.orientation = "row";
    group1.alignChildren = ["left", "center"];
    group1.spacing = 10;
    group1.margins = 0;

    var statictext1 = group1.add("statictext", undefined, undefined, { name: "statictext1" });
    statictext1.text = "insert Calender variable: ";

    var dropdown1 = group1.add("dropdownlist", undefined, undefined, { name: "dropdown1", items: getValues(CalendarVariables) });
    dropdown1.selection = 0;

    var button1 = group1.add("button", undefined, undefined, { name: "button1" });
    button1.text = "Insert";
    button1.addEventListener('click', function (e) {
      var field = getKeyByValues(CalendarVariables, dropdown1.selection.text);
      if (app.selection.length > 0) {
        var selectedObj = app.selection[0];
        if (selectedObj instanceof TextFrame) {
          // The selected object is a Text Frame
          selectedObj.label = field;
        } else if (selectedObj.hasOwnProperty('contents')) {
          // You are in typing mode inside the text box
          if(field == 'mini_month'){
            alert("Mini Month can not be inserted as a text Variable, select a text frame instaed.")
          } else {
            selectedObj.parentTextFrames[0].label = 'calendar_mix';
            app.selection[0].contents = '{{' + field + '}}';
          }
        } else {
          alert('Please select a text frame or place the cursor inside a text frame.');
        }
      } else {
        alert('Please select a text frame or place the cursor inside a text frame.');
      }
    });

    var divider1 = tab1.add("panel", undefined, undefined, { name: "divider1" });
    divider1.alignment = "fill";

    var group2 = tab1.add("group", undefined, { name: "group2" });
    group2.orientation = "row";
    group2.alignChildren = ["left", "center"];
    group2.spacing = 10;
    group2.margins = 0;

    var statictext2 = group2.add("statictext", undefined, undefined, { name: "statictext2" });
    statictext2.text = "What year? (ex. 2025) : ";

    var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
    edittext1.text = "2025";

    var button2 = group2.add("button", undefined, undefined, { name: "button2" });
    button2.text = "Generate Calendar";
    button2.addEventListener('click', function (e) {
      var y = Number(edittext1.text);
      if (y < 1970 || y > 2900 || isNaN(y)) {
        alert('Please enter a valid year!');
      } else {
        main(y, startMonthOnOddPage.value);
      }
    });

    var group3 = tab1.add("group", undefined, { name: "group3" });
    group3.orientation = "row";
    group3.alignChildren = ["left", "center"];
    group3.spacing = 10;
    group3.margins = 0;

    var startMonthOnOddPage = group3.add("checkbox", undefined, undefined, { name: "startMonthOnOddPage" });
    startMonthOnOddPage.text = "Start Month On Odd Page.";

    win.progressBar = tab1.add("progressbar", undefined, undefined, { name: "progressBar" });
    win.progressBar.maxvalue = 100;
    win.progressBar.value = 0;
    win.progressBar.preferredSize.width = 500;
    win.progressBar.preferredSize.height = 10;
    win.progressBar.alignment = ["fill","top"];

    tab1.add ('statictext', undefined, '© Mohammad Sadeea - version 1.2 - 2024');

    var tab2 = tpanel1.add("tab", undefined, undefined, { name: "tab2" });
    tab2.text = "How to use";
    tab2.orientation = "column";
    tab2.alignChildren = ["left", "top"];
    tab2.spacing = 10;
    tab2.margins = 10;

    tpanel1.selection = tab1;

    var statictext1_help = tab2.add("group", undefined, { name: "statictext1_help" });
    statictext1_help.getText = function () { var t = []; for (var n = 0; n < statictext1_help.children.length; n++) { var text = statictext1_help.children[n].text || ''; if (text === '') text = ' '; t.push(text); } return t.join('\n'); };
    statictext1_help.preferredSize.width = 350;
    statictext1_help.preferredSize.height = 200;
    statictext1_help.orientation = "column";
    statictext1_help.alignChildren = ["left", "top"];
    statictext1_help.spacing = 10;
    statictext1_help.alignment = ["fill","top"];
    
    helpStaticText = statictext1_help.add("statictext", undefined, "1- Create your calender in Master page.\n2- Define where the date, day, month and year should be on the Master-Page using \u0022Script Label\u0022 \nSimply select the text frame, then select the element you want and click insert. \nUse (date, day, day_symbol, day_f, day_a, month, month_f, month_a, month_a2, month_a3, year, year_2, hijri_date, hijri_month, hijri_month_a date_julian, month_julian, year_julian, year_2_julian, week, days_gone and days_left, mini_month) as names for the boxes . Use calendar_mix as the name for the box, then ({{date}}, {{day}}, {{day_symbol}}, {{day_f}}, {{day_a}}, {{month}}, {{month_f}}, {{month_a}}, {{month_a2}}, {{month_a3}}, {{year}}, {{year_2}}, {{date_julian}}, {{month_julian}}, {{year_julian}}, {{year_2_julian}}, {{hijri_date}}, {{hijri_month}}, {{hijri_month_a}}, {{week}}, {{days_gone}} and {{days_left}})   as names for the variables. \n3- Enter the year in YYYY format . \n4- Click \u0022Generate Calendar\u0022. \n\nMore about this script here: www.sadeea.de/generate-calendar-indesign.", {multiline: true});
    helpStaticText.preferredSize.height = 250;
    helpStaticText.alignment = ["fill","top"];
    
  }

  updateProgress(0);
  
  win.show();

}

function updateProgress(value) {
  win.progressBar.value = value;
  win.update();
}

function getKeyByValues(obj, val) {
  for (var key in obj) {
    if (obj.hasOwnProperty(key) && obj[key] == val) {
      return key;
    }
  }
}

function getValues(obj) {
  var values = [];
  for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
          values.push(obj[key]);
      }
  }
  return values;
}

function getKeys(obj) {
  var keys = [];
  for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
          keys.push(key);
      }
  }
  return keys;
}

function main(y, StartOnOdd){
  var fields = getKeys(CalendarVariables);
  delete fields['mini_month'];
  var arr = fillArr(y, StartOnOdd);
  var date, page, pageItem;
  
  document.documentPreferences.pagesPerDocument = arr.length;

  for (var idx = 0; idx < arr.length; idx++) {
    page = document.pages[idx];
    date = arr[idx];
    
    if (date.empty){
      page.appliedMaster = NothingEnum.NOTHING;
      continue;
    }

    for (var j = page.masterPageItems.length -1; j>=0; j--) {
      pageItem = page.masterPageItems[j];
      if (! pageItem.label) {
        continue;
      }

      if (pageItem.label == "calendar_mix") {
        var m = pageItem.override(page);
        for (var x = 0; x < fields.length; x++) {
          replaceText(m,'{{'+fields[x]+'}}', date[fields[x]]);
        }
      } else if (pageItem.label == "mini_month"){
        var m = pageItem.override(page);
        ensureStyles();
        m.contents = ""; // clear the contents
        createSmallMonth(m, date.date_obj, date.date, firstDayOfTheWeekForMiniMonth)
      } else if (date[pageItem.label]) {
        var m = pageItem.override(page);
        m.contents = date[pageItem.label];
      }
    }

    // Update the progress bar
    updateProgress(idx * 100 / arr.length);
  }
  win.close()
}

function fillArr(y, StartOnOdd){
  var arr = [];
  var days_gone = 1;
  var days_left = isLeapYear(y) ? 365: 364;
  var totalDaysOfMonth = [31, isLeapYear(y) ? 29: 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  for (var i = 0; i <= 11; i++) {
    for (var j = 1; j <= totalDaysOfMonth[i]; j++) {
      var date = new Date(y,i,j);
      var cal = new UQCal(date);
      var hijri = cal.convert();
      var julian_date = new Date(date.getTime() - (13 * 24 * 60 * 60 * 1000));
      var d = {
        date_obj : date,
        date : date.getDate().toString(),
        date_julian : julian_date.getDate().toString(),
        day : days[date.getDay()],
        day_f : days_f[date.getDay()],
        day_a : days_a[date.getDay()],
        day_symbol : String.fromCharCode(parseInt(days_symbol[date.getDay()].slice(1),16)),
        month : months[date.getMonth()],
        month_julian : months[julian_date.getMonth()],
        month_f : months_f[date.getMonth()],
        month_a : months_a[date.getMonth()],
        month_a2 : months_a2[date.getMonth()],
        month_a3 : months_a3[date.getMonth()],
        year : date.getFullYear().toString(),
        year_2 : date.getFullYear().toString().slice(2),
        year_julian : julian_date.getFullYear().toString(),
        year_2_julian : julian_date.getFullYear().toString().slice(2),
        hijri_date : (hijri.Hday).toString(),
        hijri_month : hijri_month[(hijri.Hmonth-1)%12],
        hijri_month_a : hijri_month_a[(hijri.Hmonth-1)%12],
        hijri_year : hijri.Hyear.toString(),
        days_gone: (days_gone).toString(),
        days_left: (days_left).toString(),
        week: getWeek(date)
      };
      arr.push(d);
      days_gone++;
      days_left--;
    }
    if(StartOnOdd && totalDaysOfMonth[i] % 2 != 0){
      arr.push({empty:true});
    }
  }
  return arr;
}

function isLeapYear(year) {
  var resp = new Date(year,1,29).getDate()===29;;
  return resp;
}

function replaceText(myObject, find, change){
  //Reset the find/change preferences before each search.
  app.changeTextPreferences = NothingEnum.nothing;
  app.findTextPreferences = NothingEnum.nothing;
  var myString = "app.findTextPreferences.properties = {findWhat:'"+ find + "'};";
  myString += "app.changeTextPreferences.properties = {changeTo:'" + change + "'};";
  app.doScript(myString, ScriptLanguage.javascript);
  myFoundItems = myObject.changeText();
  //Reset the find/change preferences after each search.
  app.changeTextPreferences = NothingEnum.nothing;
  app.findTextPreferences = NothingEnum.nothing;
}

function getWeek(d) {
  var date = new Date(d.getTime());
  date.setHours(0, 0, 0, 0);
  // Thursday in current week decides the year.
  date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
  // January 4 is always in week 1.
  var week1 = new Date(date.getFullYear(), 0, 4);
  // Adjust to Thursday in week 1 and count number of weeks from date to week1.
  return (1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
                        - 3 + (week1.getDay() + 6) % 7) / 7)).toString();
}

function ensureStyles() {
  if (!document.tableStyles.item("MiniMonthTableStyle").isValid) {
    document.tableStyles.add({ name: "MiniMonthTableStyle" });
  
    var styles = ["MiniMonthNameCellStyle", "MiniMonthHeaderCellStyle", "MiniMonthDateCellStyle", "MiniMonthTodayCellStyle"];
    for (var i = 0; i < styles.length; i++) {
      var cellStyleName = styles[i];
      
      if (!document.cellStyles.item(cellStyleName).isValid) {
        document.cellStyles.add({ name: cellStyleName });

        var ParagraphStyleName = cellStyleName.replace("Cell", "Paragraph");
        if (!document.paragraphStyles.item(ParagraphStyleName).isValid) {
          document.paragraphStyles.add({ name: ParagraphStyleName });
        }
        
        document.cellStyles.item(cellStyleName).appliedParagraphStyle = document.paragraphStyles.item(ParagraphStyleName);
      }
    }
  }
}

function createSmallMonth(textFrame, date, markToday, firstDayOfWeek){
  var currentMonthName = months[date.getMonth()];
  var currentYear = date.getFullYear();
  var currentMonth = date.getMonth();
  var daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();

  // Define table structure: 7 columns (days of the week) and 7 rows (1 for month, 1 for headers, and 5 for dates)
  var table = textFrame.insertionPoints[0].tables.add();
  table.columnCount = 7;
  table.bodyRowCount = 7;

  // Fill the top row with the month and year
  for (var i = 0; i < table.columnCount; i++) {
      table.rows[0].cells[i].contents = "";
  }
  table.rows[0].cells[0].contents = currentMonthName + " " + currentYear;
  table.rows[0].cells[0].texts[0].justification = Justification.centerAlign;
  table.rows[0].merge(table.rows[0].cells[0], table.rows[0].cells[6]);

  // Define days of the week in the second row
  var orderedDaysOfWeek = minMonth_daysOfWeek.slice(firstDayOfWeek).concat(minMonth_daysOfWeek.slice(0, firstDayOfWeek));
  for (var i = 0; i < minMonth_daysOfWeek.length; i++) {
    table.rows[1].cells[i].contents = orderedDaysOfWeek[i];
  }

  // Calculate the adjusted starting day of the month based on the selected firstDayOfWeek
  var firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay();
  var startOffset = (firstDayOfMonth - firstDayOfWeek + 7) % 7;

  // Fill in the dates for the current month, starting at the adjusted position
  var day = 1;
  for (var row = 2; row < table.bodyRowCount; row++) {
      for (var col = 0; col < table.columnCount; col++) {
          var cell = table.rows[row].cells[col];
          cell.appliedCellStyle = document.cellStyles.item("MiniMonthDateCellStyle");

          if (row === 2 && col < startOffset) {
              cell.contents = "";
          } else if (day <= daysInMonth) {
            cell.contents = day.toString();
            if(day == markToday)
              cell.appliedCellStyle = document.cellStyles.item("MiniMonthTodayCellStyle");
            day++;
          } else {
              cell.contents = "";
          }
      }
  }

  // Check for remaining days to add to the first row if needed
  var remainingDays = daysInMonth - (day - 1);
  if (remainingDays > 0) {
      for (var col = 0; col < table.columnCount; col++) {
          table.rows[2].cells[col].contents = day.toString();
          day++;
          if (day > daysInMonth) break;
      }
  }

  table.rows[0].cells.everyItem().appliedCellStyle = document.cellStyles.item("MiniMonthNameCellStyle");
  table.rows[1].cells.everyItem().appliedCellStyle = document.cellStyles.item("MiniMonthHeaderCellStyle");
  table.appliedTableStyle = document.tableStyles.item("MiniMonthTableStyle");

  // Calculate the width of each column to fit within the text frame's width
  var textFrameWidth = textFrame.geometricBounds[3] - textFrame.geometricBounds[1];
  var columnWidth = textFrameWidth / table.columnCount;
  table.columns.everyItem().width = columnWidth;
}

createProgressPanel();

/*
UQCal.js
A script for converting Gregorian date to Hijri date (Umm Alqura calculation).
Version: 1.0, 28/12/2013
https://github.com/talomaireeni/Umm-Al-Qura-Calendar
Licensed 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.

Credit:
Original code: http://www.staff.science.uu.nl/~gent0113/islam/ummalqura_converter.htm
*/

function UQCal(d) {
  var ummalqura_dat = [28607, 28636, 28665, 28695, 28724, 28754, 28783, 28813, 28843, 28872, 28901, 28931, 28960, 28990, 29019, 29049, 29078, 29108, 29137, 29167,
    29196, 29226, 29255, 29285, 29315, 29345, 29375, 29404, 29434, 29463, 29492, 29522, 29551, 29580, 29610, 29640, 29669, 29699, 29729, 29759,
    29788, 29818, 29847, 29876, 29906, 29935, 29964, 29994, 30023, 30053, 30082, 30112, 30141, 30171, 30200, 30230, 30259, 30289, 30318, 30348,
    30378, 30408, 30437, 30467, 30496, 30526, 30555, 30585, 30614, 30644, 30673, 30703, 30732, 30762, 30791, 30821, 30850, 30880, 30909, 30939,
    30968, 30998, 31027, 31057, 31086, 31116, 31145, 31175, 31204, 31234, 31263, 31293, 31322, 31352, 31381, 31411, 31441, 31471, 31500, 31530,
    31559, 31589, 31618, 31648, 31676, 31706, 31736, 31766, 31795, 31825, 31854, 31884, 31913, 31943, 31972, 32002, 32031, 32061, 32090, 32120,
    32150, 32180, 32209, 32239, 32268, 32298, 32327, 32357, 32386, 32416, 32445, 32475, 32504, 32534, 32563, 32593, 32622, 32652, 32681, 32711,
    32740, 32770, 32799, 32829, 32858, 32888, 32917, 32947, 32976, 33006, 33035, 33065, 33094, 33124, 33153, 33183, 33213, 33243, 33272, 33302,
    33331, 33361, 33390, 33420, 33450, 33479, 33509, 33539, 33568, 33598, 33627, 33657, 33686, 33716, 33745, 33775, 33804, 33834, 33863, 33893,
    33922, 33952, 33981, 34011, 34040, 34069, 34099, 34128, 34158, 34187, 34217, 34247, 34277, 34306, 34336, 34365, 34395, 34424, 34454, 34483,
    34512, 34542, 34571, 34601, 34631, 34660, 34690, 34719, 34749, 34778, 34808, 34837, 34867, 34896, 34926, 34955, 34985, 35015, 35044, 35074,
    35103, 35133, 35162, 35192, 35222, 35251, 35280, 35310, 35340, 35370, 35399, 35429, 35458, 35488, 35517, 35547, 35576, 35605, 35635, 35665,
    35694, 35723, 35753, 35782, 35811, 35841, 35871, 35901, 35930, 35960, 35989, 36019, 36048, 36078, 36107, 36136, 36166, 36195, 36225, 36254,
    36284, 36314, 36343, 36373, 36403, 36433, 36462, 36492, 36521, 36551, 36580, 36610, 36639, 36669, 36698, 36728, 36757, 36786, 36816, 36845,
    36875, 36904, 36934, 36963, 36993, 37022, 37052, 37081, 37111, 37141, 37170, 37200, 37229, 37259, 37288, 37318, 37347, 37377, 37406, 37436,
    37465, 37495, 37524, 37554, 37584, 37613, 37643, 37672, 37701, 37731, 37760, 37790, 37819, 37849, 37878, 37908, 37938, 37967, 37997, 38027,
    38056, 38085, 38115, 38144, 38174, 38203, 38233, 38262, 38292, 38322, 38351, 38381, 38410, 38440, 38469, 38499, 38528, 38558, 38587, 38617,
    38646, 38676, 38705, 38735, 38764, 38794, 38823, 38853, 38882, 38912, 38941, 38971, 39001, 39030, 39059, 39089, 39118, 39148, 39178, 39208,
    39237, 39267, 39297, 39326, 39355, 39385, 39414, 39444, 39473, 39503, 39532, 39562, 39592, 39621, 39650, 39680, 39709, 39739, 39768, 39798,
    39827, 39857, 39886, 39916, 39946, 39975, 40005, 40035, 40064, 40094, 40123, 40153, 40182, 40212, 40241, 40271, 40300, 40330, 40359, 40389,
    40418, 40448, 40477, 40507, 40536, 40566, 40595, 40625, 40655, 40685, 40714, 40744, 40773, 40803, 40832, 40862, 40892, 40921, 40951, 40980,
    41009, 41039, 41068, 41098, 41127, 41157, 41186, 41216, 41245, 41275, 41304, 41334, 41364, 41393, 41422, 41452, 41481, 41511, 41540, 41570,
    41599, 41629, 41658, 41688, 41718, 41748, 41777, 41807, 41836, 41865, 41894, 41924, 41953, 41983, 42012, 42042, 42072, 42102, 42131, 42161,
    42190, 42220, 42249, 42279, 42308, 42337, 42367, 42397, 42426, 42456, 42485, 42515, 42545, 42574, 42604, 42633, 42662, 42692, 42721, 42751,
    42780, 42810, 42839, 42869, 42899, 42929, 42958, 42988, 43017, 43046, 43076, 43105, 43135, 43164, 43194, 43223, 43253, 43283, 43312, 43342,
    43371, 43401, 43430, 43460, 43489, 43519, 43548, 43578, 43607, 43637, 43666, 43696, 43726, 43755, 43785, 43814, 43844, 43873, 43903, 43932,
    43962, 43991, 44021, 44050, 44080, 44109, 44139, 44169, 44198, 44228, 44258, 44287, 44317, 44346, 44375, 44405, 44434, 44464, 44493, 44523,
    44553, 44582, 44612, 44641, 44671, 44700, 44730, 44759, 44788, 44818, 44847, 44877, 44906, 44936, 44966, 44996, 45025, 45055, 45084, 45114,
    45143, 45172, 45202, 45231, 45261, 45290, 45320, 45350, 45380, 45409, 45439, 45468, 45498, 45527, 45556, 45586, 45615, 45644, 45674, 45704,
    45733, 45763, 45793, 45823, 45852, 45882, 45911, 45940, 45970, 45999, 46028, 46058, 46088, 46117, 46147, 46177, 46206, 46236, 46265, 46295,
    46324, 46354, 46383, 46413, 46442, 46472, 46501, 46531, 46560, 46590, 46620, 46649, 46679, 46708, 46738, 46767, 46797, 46826, 46856, 46885,
    46915, 46944, 46974, 47003, 47033, 47063, 47092, 47122, 47151, 47181, 47210, 47240, 47269, 47298, 47328, 47357, 47387, 47417, 47446, 47476,
    47506, 47535, 47565, 47594, 47624, 47653, 47682, 47712, 47741, 47771, 47800, 47830, 47860, 47890, 47919, 47949, 47978, 48008, 48037, 48066,
    48096, 48125, 48155, 48184, 48214, 48244, 48273, 48303, 48333, 48362, 48392, 48421, 48450, 48480, 48509, 48538, 48568, 48598, 48627, 48657,
    48687, 48717, 48746, 48776, 48805, 48834, 48864, 48893, 48922, 48952, 48982, 49011, 49041, 49071, 49100, 49130, 49160, 49189, 49218, 49248,
    49277, 49306, 49336, 49365, 49395, 49425, 49455, 49484, 49514, 49543, 49573, 49602, 49632, 49661, 49690, 49720, 49749, 49779, 49809, 49838,
    49868, 49898, 49927, 49957, 49986, 50016, 50045, 50075, 50104, 50133, 50163, 50192, 50222, 50252, 50281, 50311, 50340, 50370, 50400, 50429,
    50459, 50488, 50518, 50547, 50576, 50606, 50635, 50665, 50694, 50724, 50754, 50784, 50813, 50843, 50872, 50902, 50931, 50960, 50990, 51019,
    51049, 51078, 51108, 51138, 51167, 51197, 51227, 51256, 51286, 51315, 51345, 51374, 51403, 51433, 51462, 51492, 51522, 51552, 51582, 51611,
    51641, 51670, 51699, 51729, 51758, 51787, 51816, 51846, 51876, 51906, 51936, 51965, 51995, 52025, 52054, 52083, 52113, 52142, 52171, 52200,
    52230, 52260, 52290, 52319, 52349, 52379, 52408, 52438, 52467, 52497, 52526, 52555, 52585, 52614, 52644, 52673, 52703, 52733, 52762, 52792,
    52822, 52851, 52881, 52910, 52939, 52969, 52998, 53028, 53057, 53087, 53116, 53146, 53176, 53205, 53235, 53264, 53294, 53324, 53353, 53383,
    53412, 53441, 53471, 53500, 53530, 53559, 53589, 53619, 53648, 53678, 53708, 53737, 53767, 53796, 53825, 53855, 53884, 53913, 53943, 53973,
    54003, 54032, 54062, 54092, 54121, 54151, 54180, 54209, 54239, 54268, 54297, 54327, 54357, 54387, 54416, 54446, 54476, 54505, 54535, 54564,
    54593, 54623, 54652, 54681, 54711, 54741, 54770, 54800, 54830, 54859, 54889, 54919, 54948, 54977, 55007, 55036, 55066, 55095, 55125, 55154,
    55184, 55213, 55243, 55273, 55302, 55332, 55361, 55391, 55420, 55450, 55479, 55508, 55538, 55567, 55597, 55627, 55657, 55686, 55716, 55745,
    55775, 55804, 55834, 55863, 55892, 55922, 55951, 55981, 56011, 56040, 56070, 56100, 56129, 56159, 56188, 56218, 56247, 56276, 56306, 56335,
    56365, 56394, 56424, 56454, 56483, 56513, 56543, 56572, 56601, 56631, 56660, 56690, 56719, 56749, 56778, 56808, 56837, 56867, 56897, 56926,
    56956, 56985, 57015, 57044, 57074, 57103, 57133, 57162, 57192, 57221, 57251, 57280, 57310, 57340, 57369, 57399, 57429, 57458, 57487, 57517,
    57546, 57576, 57605, 57634, 57664, 57694, 57723, 57753, 57783, 57813, 57842, 57871, 57901, 57930, 57959, 57989, 58018, 58048, 58077, 58107,
    58137, 58167, 58196, 58226, 58255, 58285, 58314, 58343, 58373, 58402, 58432, 58461, 58491, 58521, 58551, 58580, 58610, 58639, 58669, 58698,
    58727, 58757, 58786, 58816, 58845, 58875, 58905, 58934, 58964, 58994, 59023, 59053, 59082, 59111, 59141, 59170, 59200, 59229, 59259, 59288,
    59318, 59348, 59377, 59407, 59436, 59466, 59495, 59525, 59554, 59584, 59613, 59643, 59672, 59702, 59731, 59761, 59791, 59820, 59850, 59879,
    59909, 59939, 59968, 59997, 60027, 60056, 60086, 60115, 60145, 60174, 60204, 60234, 60264, 60293, 60323, 60352, 60381, 60411, 60440, 60469,
    60499, 60528, 60558, 60588, 60618, 60648, 60677, 60707, 60736, 60765, 60795, 60824, 60853, 60883, 60912, 60942, 60972, 61002, 61031, 61061,
    61090, 61120, 61149, 61179, 61208, 61237, 61267, 61296, 61326, 61356, 61385, 61415, 61445, 61474, 61504, 61533, 61563, 61592, 61621, 61651,
    61680, 61710, 61739, 61769, 61799, 61828, 61858, 61888, 61917, 61947, 61976, 62006, 62035, 62064, 62094, 62123, 62153, 62182, 62212, 62242,
    62271, 62301, 62331, 62360, 62390, 62419, 62448, 62478, 62507, 62537, 62566, 62596, 62625, 62655, 62685, 62715, 62744, 62774, 62803, 62832,
    62862, 62891, 62921, 62950, 62980, 63009, 63039, 63069, 63099, 63128, 63157, 63187, 63216, 63246, 63275, 63305, 63334, 63363, 63393, 63423,
    63453, 63482, 63512, 63541, 63571, 63600, 63630, 63659, 63689, 63718, 63747, 63777, 63807, 63836, 63866, 63895, 63925, 63955, 63984, 64014,
    64043, 64073, 64102, 64131, 64161, 64190, 64220, 64249, 64279, 64309, 64339, 64368, 64398, 64427, 64457, 64486, 64515, 64545, 64574, 64603,
    64633, 64663, 64692, 64722, 64752, 64782, 64811, 64841, 64870, 64899, 64929, 64958, 64987, 65017, 65047, 65076, 65106, 65136, 65166, 65195,
    65225, 65254, 65283, 65313, 65342, 65371, 65401, 65431, 65460, 65490, 65520, 65549, 65579, 65608, 65638, 65667, 65697, 65726, 65755, 65785,
    65815, 65844, 65874, 65903, 65933, 65963, 65992, 66022, 66051, 66081, 66110, 66140, 66169, 66199, 66228, 66258, 66287, 66317, 66346, 66376,
    66405, 66435, 66465, 66494, 66524, 66553, 66583, 66612, 66641, 66671, 66700, 66730, 66760, 66789, 66819, 66849, 66878, 66908, 66937, 66967,
    66996, 67025, 67055, 67084, 67114, 67143, 67173, 67203, 67233, 67262, 67292, 67321, 67351, 67380, 67409, 67439, 67468, 67497, 67527, 67557,
    67587, 67617, 67646, 67676, 67705, 67735, 67764, 67793, 67823, 67852, 67882, 67911, 67941, 67971, 68000, 68030, 68060, 68089, 68119, 68148,
    68177, 68207, 68236, 68266, 68295, 68325, 68354, 68384, 68414, 68443, 68473, 68502, 68532, 68561, 68591, 68620, 68650, 68679, 68708, 68738,
    68768, 68797, 68827, 68857, 68886, 68916, 68946, 68975, 69004, 69034, 69063, 69092, 69122, 69152, 69181, 69211, 69240, 69270, 69300, 69330,
    69359, 69388, 69418, 69447, 69476, 69506, 69535, 69565, 69595, 69624, 69654, 69684, 69713, 69743, 69772, 69802, 69831, 69861, 69890, 69919,
    69949, 69978, 70008, 70038, 70067, 70097, 70126, 70156, 70186, 70215, 70245, 70274, 70303, 70333, 70362, 70392, 70421, 70451, 70481, 70510,
    70540, 70570, 70599, 70629, 70658, 70687, 70717, 70746, 70776, 70805, 70835, 70864, 70894, 70924, 70954, 70983, 71013, 71042, 71071, 71101,
    71130, 71159, 71189, 71218, 71248, 71278, 71308, 71337, 71367, 71397, 71426, 71455, 71485, 71514, 71543, 71573, 71602, 71632, 71662, 71691,
    71721, 71751, 71781, 71810, 71839, 71869, 71898, 71927, 71957, 71986, 72016, 72046, 72075, 72105, 72135, 72164, 72194, 72223, 72253, 72282,
    72311, 72341, 72370, 72400, 72429, 72459, 72489, 72518, 72548, 72577, 72607, 72637, 72666, 72695, 72725, 72754, 72784, 72813, 72843, 72872,
    72902, 72931, 72961, 72991, 73020, 73050, 73080, 73109, 73139, 73168, 73197, 73227, 73256, 73286, 73315, 73345, 73375, 73404, 73434, 73464,
    73493, 73523, 73552, 73581, 73611, 73640, 73669, 73699, 73729, 73758, 73788, 73818, 73848, 73877, 73907, 73936, 73965, 73995, 74024, 74053,
    74083, 74113, 74142, 74172, 74202, 74231, 74261, 74291, 74320, 74349, 74379, 74408, 74437, 74467, 74497, 74526, 74556, 74586, 74615, 74645,
    74675, 74704, 74733, 74763, 74792, 74822, 74851, 74881, 74910, 74940, 74969, 74999, 75029, 75058, 75088, 75117, 75147, 75176, 75206, 75235,
    75264, 75294, 75323, 75353, 75383, 75412, 75442, 75472, 75501, 75531, 75560, 75590, 75619, 75648, 75678, 75707, 75737, 75766, 75796, 75826,
    75856, 75885, 75915, 75944, 75974, 76003, 76032, 76062, 76091, 76121, 76150, 76180, 76210, 76239, 76269, 76299, 76328, 76358, 76387, 76416,
    76446, 76475, 76505, 76534, 76564, 76593, 76623, 76653, 76682, 76712, 76741, 76771, 76801, 76830, 76859, 76889, 76918, 76948, 76977, 77007,
    77036, 77066, 77096, 77125, 77155, 77185, 77214, 77243, 77273, 77302, 77332, 77361, 77390, 77420, 77450, 77479, 77509, 77539, 77569, 77598,
    77627, 77657, 77686, 77715, 77745, 77774, 77804, 77833, 77863, 77893, 77923, 77952, 77982, 78011, 78041, 78070, 78099, 78129, 78158, 78188,
    78217, 78247, 78277, 78307, 78336, 78366, 78395, 78425, 78454, 78483, 78513, 78542, 78572, 78601, 78631, 78661, 78690, 78720, 78750, 78779,
    78808, 78838, 78867, 78897, 78926, 78956, 78985, 79015, 79044, 79074, 79104, 79133, 79163, 79192, 79222, 79251, 79281, 79310, 79340, 79369,
    79399, 79428, 79458, 79487, 79517, 79546, 79576, 79606, 79635, 79665, 79695, 79724, 79753, 79783, 79812, 79841, 79871, 79900, 79930, 79960,
    79990];
  var date = (d ? new Date(d) : new Date());

  this.Gday = date.getDate();
  this.Gmonth = date.getMonth();
  this.Gyear = date.getFullYear();
  this.Hday = null;
  this.Hmonth = null;
  this.Hyear = null;
  this.ilunnum = null;
  this.Hlength = null;
  this.julday = null;
  this.wkday = null;

  this.gmod = function(n, m) {
        // generalized modulo function (n mod m) also valid for negative values of n
        return ((n % m) + m) % m;
  }

  this.convert = function() {
        day = this.Gday;
        month = this.Gmonth;
        year = this.Gyear;

        m = month + 1;
        y = year;

        if (m < 3) {
              y -= 1;
              m += 12;
        }

        // determine offset between Julian and Gregorian calendar 
        a = Math.floor(y / 100.);
        jgc = a - Math.floor(a / 4.) - 2;

        // compute Chronological Julian Day Number (CJDN)
        cjdn = Math.floor(365.25 * (y + 4716)) + Math.floor(30.6001 * (m + 1)) + day - jgc - 1524;

        a = Math.floor((cjdn - 1867216.25) / 36524.25);
        jgc = a - Math.floor(a / 4.) + 1;
        b = cjdn + jgc + 1524;
        c = Math.floor((b - 122.1) / 365.25);
        d = Math.floor(365.25 * c);
        month = Math.floor((b - d) / 30.6001);
        day = (b - d) - Math.floor(30.6001 * month);

        if (month > 13) {
              c += 1;
              month -= 12;
        }

        month -= 1;
        year = c - 4716;

        //  output Western calendar date
        this.Gday = day;
        this.Gmonth = month;
        this.Gyear = year;

        // compute weekday
        wd = this.gmod(cjdn + 1, 7) + 1;

        // output Chronological Julian Day Number and weekday
        this.julday = cjdn;
        this.wkday = wd - 1;

        // compute Modified Chronological Julian Day Number (MCJDN)
        mcjdn = cjdn - 2400000;

        // the MCJDN's of the start of the lunations in the Umm al-Qura calendar are stored in 'islamcalendar_dat.js'  
        for (var i = 0; i < ummalqura_dat.length; i++) {
              if (ummalqura_dat[i] > mcjdn) break;
        }

        // compute and output the Umm al-Qura calendar date
        iln = i + 16260;
        ii = Math.floor((iln - 1) / 12);
        iy = ii + 1;
        im = iln - 12 * ii;
        id = mcjdn - ummalqura_dat[i - 1] + 1;
        ml = ummalqura_dat[i] - ummalqura_dat[i - 1];

        this.Hday = id;
        this.Hmonth = im;
        this.Hyear = iy;
        this.ilunnum = iln;
        this.Hlength = ml;

        return this;
  }
}
