// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource

namespace System.Globalization
{

    using System;
    using System.Diagnostics.Contracts;
	using System.Diagnostics;

// 


    //
    // List of calendar data
    // Note the we cache overrides.
    // Note that localized names (resource names) aren't available from here.
    //
    //  NOTE: Calendars depend on the locale name that creates it.  Only a few
    //            properties are available without locales using CalendarData.GetCalendar(int)

    // StructLayout is needed here otherwise compiler can re-arrange the fields.
    // We have to keep this in-[....] with the definition in calendardata.h
    //
    // WARNING WARNING WARNING
    //
    // WARNING: Anything changed here also needs to be updated on the native side (object.h see type CalendarDataBaseObject)
    // WARNING: The type loader will rearrange class member offsets so the mscorwks!CalendarDataBaseObject
    // WARNING: must be manually structured to match the true loaded class layout
    //
    class CalendarData
    {
        // Max calendars
        const int MAX_CALENDARS = 23;

        // Identity
        String     sNativeName              ~ delete _; // Calendar Name for the locale

        // Formats
        String[]   saShortDates             ~ DeleteContainerAndItems!(_); // Short Data format, default first
        String[]   saYearMonths             ~ DeleteContainerAndItems!(_); // Year/Month Data format, default first
        String[]   saLongDates              ~ DeleteContainerAndItems!(_); // Long Data format, default first
        String     sMonthDay                ~ delete _; // Month/Day format

        // Calendar Parts Names
        String[]   saEraNames               ~ DeleteContainerAndItems!(_); // Names of Eras
        String[]   saAbbrevEraNames         ~ DeleteContainerAndItems!(_); // Abbreviated Era Names
        String[]   saAbbrevEnglishEraNames  ~ DeleteContainerAndItems!(_); // Abbreviated Era Names in English
        String[]   saDayNames               ~ DeleteContainerAndItems!(_); // Day Names, null to use locale data, starts on Sunday
        String[]   saAbbrevDayNames         ~ DeleteContainerAndItems!(_); // Abbrev Day Names, null to use locale data, starts on Sunday
        String[]   saSuperShortDayNames     ~ DeleteContainerAndItems!(_); // Super short Day of week names
        String[]   saMonthNames             ~ DeleteContainerAndItems!(_); // Month Names (13)
        String[]   saAbbrevMonthNames       ~ DeleteContainerAndItems!(_); // Abbrev Month Names (13)
        String[]   saMonthGenitiveNames     ~ DeleteContainerAndItems!(_); // Genitive Month Names (13)
        String[]   saAbbrevMonthGenitiveNames~ DeleteContainerAndItems!(_); // Genitive Abbrev Month Names (13)
        String[]   saLeapYearMonthNames     ~ DeleteContainerAndItems!(_); // Multiple strings for the month names in a leap year.

        // Integers at end to make marshaller happier
        int        iTwoDigitYearMax=2029    ; // Max 2 digit year (for Y2K bug data entry)
        int        iCurrentEra=0            ;  // current era # (usually 1)

        // Use overrides?
        bool       bUseUserOverrides        ; // True if we want user overrides.

        // Static invariant for the invariant locale
        static CalendarData Invariant ~ delete _;

        // Private constructor
        private this() {}

        // Invariant constructor
        static this()
        {

            // Set our default/gregorian US calendar data
            // Calendar IDs are 1-based, arrays are 0 based.
            CalendarData invariant = new CalendarData();

            invariant.SetupDefaults();

            // Calendar was built, go ahead and assign it...            
            Invariant = invariant;
        }

		void SetupDefaults()
		{
			// Set default data for calendar
			// Note that we don't load resources since this IS NOT supposed to change (by definition)
			sNativeName           = new String("Gregorian Calendar");  // Calendar Name

			// Year
			iTwoDigitYearMax      = 2029; // Max 2 digit year (for Y2K bug data entry)
			iCurrentEra           = 1; // Current era #

			// Formats
			saShortDates          = AllocStrings("MM/dd/yyyy", "yyyy-MM-dd");          // short date format
			saLongDates           = AllocStrings("dddd, dd MMMM yyyy");                 // long date format
			saYearMonths          = AllocStrings("yyyy MMMM");                         // year month format
			sMonthDay             = new String("MMMM dd");                                            // Month day pattern

			// Calendar Parts Names
			saEraNames            = AllocStrings("A.D.");     // Era names
			saAbbrevEraNames      = AllocStrings("AD");      // Abbreviated Era names
			saAbbrevEnglishEraNames=AllocStrings("AD");     // Abbreviated era names in English
			saDayNames            = AllocStrings("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");// day names
			saAbbrevDayNames      = AllocStrings("Sun",    "Mon",    "Tue",     "Wed",       "Thu",      "Fri",    "Sat");     // abbreviated day names
			saSuperShortDayNames  = AllocStrings("Su",     "Mo",     "Tu",      "We",        "Th",       "Fr",     "Sa");      // The super short day names
			saMonthNames          = AllocStrings("January", "February", "March", "April", "May", "June", 
			                                                "July", "August", "September", "October", "November", "December", String.Empty); // month names
			saAbbrevMonthNames    = AllocStrings("Jan", "Feb", "Mar", "Apr", "May", "Jun",
			                                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", String.Empty); // abbreviated month names
			saMonthGenitiveNames  = AllocStrings(params saMonthNames);              // Genitive month names (same as month names for invariant)
			saAbbrevMonthGenitiveNames=AllocStrings(params saAbbrevMonthNames);    // Abbreviated genitive month names (same as abbrev month names for invariant)
			saLeapYearMonthNames  = AllocStrings(params saMonthNames);              // leap year month names are unused in Gregorian English (invariant)

			bUseUserOverrides     = false;
		}

		static String[] AllocStrings(params String[] strs)
		{
			String[] newStrs = new String[strs.Count];
			for (var str in strs)
				newStrs[@str] = new String(str);
			return newStrs;
		}

        //
        // Get a bunch of data for a calendar
        //
        public this(String localeName, int calendarId, bool bUseUserOverrides)
        {
			String[] Clone(String[] strs)
			{
				var newStrs = new String[strs.Count];
				for (var str in strs)
					newStrs[@str] = new String(str);
				return newStrs;
			}

            // Call nativeGetCalendarData to populate the data
            this.bUseUserOverrides = bUseUserOverrides;
            if (!nativeGetCalendarData(this, localeName, calendarId))
            {
                //Contract.Assert(false, "[CalendarData] nativeGetCalendarData call isn't expected to fail for calendar " + calendarId + " locale " +localeName);
				Debug.FatalError("[CalendarData] nativeGetCalendarData call isn't expected to fail for calendar");
                // Something failed, try invariant for missing parts
                // This is really not good, but we don't want the callers to crash.
#unwarn
                if (this.sNativeName == null)   this.sNativeName  = String.Empty;           // Calendar Name for the locale.
                
                // Formats
                if (this.saShortDates == null)  this.saShortDates = Clone(Invariant.saShortDates); // Short Data format, default first
                if (this.saYearMonths == null)  this.saYearMonths = Clone(Invariant.saYearMonths); // Year/Month Data format, default first
                if (this.saLongDates == null)   this.saLongDates  = Clone(Invariant.saLongDates);  // Long Data format, default first
                if (this.sMonthDay == null)     this.sMonthDay    = Invariant.sMonthDay;    // Month/Day format
                
                // Calendar Parts Names
                if (this.saEraNames == null)              this.saEraNames              = Invariant.saEraNames;              // Names of Eras
                if (this.saAbbrevEraNames == null)        this.saAbbrevEraNames        = Invariant.saAbbrevEraNames;        // Abbreviated Era Names
                if (this.saAbbrevEnglishEraNames == null) this.saAbbrevEnglishEraNames = Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
                if (this.saDayNames == null)              this.saDayNames              = Invariant.saDayNames;              // Day Names, null to use locale data, starts on Sunday
                if (this.saAbbrevDayNames == null)        this.saAbbrevDayNames        = Invariant.saAbbrevDayNames;        // Abbrev Day Names, null to use locale data, starts on Sunday
                if (this.saSuperShortDayNames == null)    this.saSuperShortDayNames    = Invariant.saSuperShortDayNames;    // Super short Day of week names
                if (this.saMonthNames == null)            this.saMonthNames            = Invariant.saMonthNames;            // Month Names (13)
                if (this.saAbbrevMonthNames == null)      this.saAbbrevMonthNames      = Invariant.saAbbrevMonthNames;      // Abbrev Month Names (13)
                // Genitive and Leap names can follow the fallback below
            }

            // Clean up the escaping of the formats
            
			CultureData.[Friend]ReescapeWin32Strings(this.saShortDates);
            CultureData.[Friend]ReescapeWin32Strings(this.saLongDates);
            CultureData.[Friend]ReescapeWin32Strings(this.saYearMonths);
            CultureData.[Friend]ReescapeWin32String(this.sMonthDay);

            if ((CalendarId)calendarId == CalendarId.TAIWAN)
            {
                // for Geo----al reasons, the ----ese native name should only be returned when 
                // for ----ese SKU
                if (CultureInfo.[Friend]IsTaiwanSku)
                {
                    // We got the month/day names from the OS (same as gregorian), but the native name is wrong
                    this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
                }
                else
                {
                    this.sNativeName = String.Empty;
                }
            }

            // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
            if (this.saMonthGenitiveNames == null || String.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
                this.saMonthGenitiveNames = this.saMonthNames;              // Genitive month names (same as month names for invariant)
            if (this.saAbbrevMonthGenitiveNames == null || String.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
                this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames;    // Abbreviated genitive month names (same as abbrev month names for invariant)
            if (this.saLeapYearMonthNames == null || String.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
                this.saLeapYearMonthNames = this.saMonthNames;

            InitializeEraNames(localeName, calendarId);

            InitializeAbbreviatedEraNames(localeName, calendarId);

            // Abbreviated English Era Names are only used for the Japanese calendar.
            if (calendarId == (int)CalendarId.JAPAN)
            {
                //this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
				Runtime.NotImplemented();
            }
            else
            {
                // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
                this.saAbbrevEnglishEraNames = new String[] ( "" );
            }

            // Japanese is the only thing with > 1 era.  Its current era # is how many ever 
            // eras are in the array.  (And the others all have 1 string in the array)
            this.iCurrentEra = this.saEraNames.Count;
        }

        private void InitializeEraNames(String localeName, int calendarId)
        {
            // Note that the saEraNames only include "A.D."  We don't have localized names for other calendars available from windows
            switch ((CalendarId)calendarId)
            {
                // For Localized Gregorian we really expect the data from the OS.
            case CalendarId.GREGORIAN:
                // Fallback for CoreCLR < Win7 or culture.dll missing            
                if (this.saEraNames == null || this.saEraNames.Count == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
                {
					DeleteContainerAndItems!(this.saEraNames);
                    this.saEraNames = AllocStrings("A.D.");
                }
                break;

                // The rest of the calendars have constant data, so we'll just use that
            case CalendarId.GREGORIAN_US:
            case CalendarId.JULIAN:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("A.D.");
                break;
            case CalendarId.HEBREW:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("C.E.");
                break;
            case CalendarId.HIJRI:
            case CalendarId.UMALQURA:
				DeleteContainerAndItems!(this.saEraNames);
                if (localeName == "dv-MV")
                {
                    // Special case for Divehi
                    this.saEraNames = AllocStrings("\x0780\x07a8\x0796\x07b0\x0783\x07a9");
                }
                else
                {
                    this.saEraNames = AllocStrings("\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629");
                }
                break;
            case CalendarId.GREGORIAN_ARABIC:
            case CalendarId.GREGORIAN_XLIT_ENGLISH:
            case CalendarId.GREGORIAN_XLIT_FRENCH:
                // These are all the same:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("\x0645");
                break;

            case CalendarId.GREGORIAN_ME_FRENCH:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("ap. J.-C.");
                break;
                
            case CalendarId.TAIWAN:
                // for Geo----al reasons, the ----ese native name should only be returned when 
                // for ----ese SKU
				DeleteContainerAndItems!(this.saEraNames);
                if (CultureInfo.[Friend]IsTaiwanSku)
                {
                    // 
                    this.saEraNames = AllocStrings("\x4e2d\x83ef\x6c11\x570b");
                }
                else
                {
                    this.saEraNames = AllocStrings(String.Empty);
                }
                break;

            case CalendarId.KOREA:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("\xb2e8\xae30");
                break;
                
            case CalendarId.THAI:
				DeleteContainerAndItems!(this.saEraNames);
                this.saEraNames = AllocStrings("\x0e1e\x002e\x0e28\x002e");
                break;
                
            case CalendarId.JAPAN:
            case CalendarId.JAPANESELUNISOLAR:
                //this.saEraNames = JapaneseCalendar.EraNames();
				Runtime.NotImplemented();
                //break;

            case CalendarId.PERSIAN:
                if (this.saEraNames == null || this.saEraNames.Count == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
                {
					DeleteContainerAndItems!(this.saEraNames);
                    this.saEraNames = AllocStrings("\x0647\x002e\x0634");
                }
                break;

            default:
                // Most calendars are just "A.D."
                this.saEraNames = Invariant.saEraNames;
                break;
            }
        }

        private void InitializeAbbreviatedEraNames(StringView localeName, int calendarId)
        {
            // Note that the saAbbrevEraNames only include "AD"  We don't have localized names for other calendars available from windows
            switch ((CalendarId)calendarId)
            {
                // For Localized Gregorian we really expect the data from the OS.
            case CalendarId.GREGORIAN:
                // Fallback for CoreCLR < Win7 or culture.dll missing            
                if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Count == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
                {
					DeleteContainerAndItems!(this.saAbbrevEraNames);
                    this.saAbbrevEraNames = AllocStrings("AD");
                }
            
                // The rest of the calendars have constant data, so we'll just use that
            case CalendarId.GREGORIAN_US:
            case CalendarId.JULIAN:
				DeleteContainerAndItems!(this.saAbbrevEraNames);
                this.saAbbrevEraNames = AllocStrings("AD");
                break;                    
            case CalendarId.JAPAN:
            case CalendarId.JAPANESELUNISOLAR:
                //this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
				Runtime.NotImplemented();
            case CalendarId.HIJRI:
            case CalendarId.UMALQURA:
				DeleteContainerAndItems!(this.saAbbrevEraNames);
                if (localeName == "dv-MV")
                {
                    // Special case for Divehi
                    this.saAbbrevEraNames = AllocStrings("\x0780\x002e");
                }
                else
                {
                    this.saAbbrevEraNames = AllocStrings("\x0647\x0640");
                }
                break;
            case CalendarId.TAIWAN:
                // Get era name and abbreviate it
				DeleteContainerAndItems!(this.saAbbrevEraNames);
                this.saAbbrevEraNames = new String[1];
                if (this.saEraNames[0].Length == 4)
                {
                    this.saAbbrevEraNames[0] = new String(this.saEraNames[0], 2, 2);
                }
                else
                {
                    this.saAbbrevEraNames[0] = new String(this.saEraNames[0]);
                }                        
                break;

            case CalendarId.PERSIAN:
                if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Count == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
                {
					DeleteContainerAndItems!(this.saAbbrevEraNames);
                    this.saAbbrevEraNames = AllocStrings(params this.saEraNames);
                }
                break;

            default:
                // Most calendars just use the full name
                this.saAbbrevEraNames = AllocStrings(params this.saEraNames);
                break;
            }
        }

        static CalendarData GetCalendarData(int calendarId)
        {
            /*//
            // Get a calendar.
            // Unfortunately we depend on the locale in the OS, so we need a locale
            // no matter what.  So just get the appropriate calendar from the 
            // appropriate locale here
            //

            // Get a culture name
            // 
            String culture = CalendarIdToCultureName(calendarId);
           
            // Return our calendar
            return CultureInfo.GetCultureInfo(culture).m_cultureData.GetCalendar(calendarId);*/
			Runtime.FatalError();
        }

        //
        // Helper methods
        //
        private static String CalendarIdToCultureName(int calendarId)
        {
            switch (calendarId)
            {
                case Calendar.[Friend]CAL_GREGORIAN_US: 
                    return "fa-IR";             // "fa-IR" Iran
                    
                case Calendar.[Friend]CAL_JAPAN:
                    return "ja-JP";             // "ja-JP" Japan
                
                case Calendar.[Friend]CAL_TAIWAN:
                    return "zh-TW";             // zh-TW Taiwan
                
                case Calendar.[Friend]CAL_KOREA:            
                    return "ko-KR";             // "ko-KR" Korea
                    
                case Calendar.[Friend]CAL_HIJRI:
                case Calendar.[Friend]CAL_GREGORIAN_ARABIC:
                case Calendar.[Friend]CAL_UMALQURA:
                    return "ar-SA";             // "ar-SA" Saudi Arabia

                case Calendar.[Friend]CAL_THAI:
                    return "th-TH";             // "th-TH" Thailand
                    
                case Calendar.[Friend]CAL_HEBREW:
                    return "he-IL";             // "he-IL" Israel
                    
                case Calendar.[Friend]CAL_GREGORIAN_ME_FRENCH:
                    return "ar-DZ";             // "ar-DZ" Algeria
                
                case Calendar.[Friend]CAL_GREGORIAN_XLIT_ENGLISH:
                case Calendar.[Friend]CAL_GREGORIAN_XLIT_FRENCH:
                    return "ar-IQ";             // "ar-IQ"; Iraq
                
                default:
                    // Default to gregorian en-US
                    break;
            }

            return "en-US";
        }

        /*internal void FixupWin7MonthDaySemicolonBug()
        {
            int unescapedCharacterIndex = FindUnescapedCharacter(sMonthDay, ';');
            if (unescapedCharacterIndex > 0)
            {
                sMonthDay = sMonthDay.Substring(0, unescapedCharacterIndex);
            }
        }*/

        private static int FindUnescapedCharacter(String s, char8 charToFind)
        {
            bool inComment = false;
            int length = s.Length;
            for (int i = 0; i < length; i++)
            {
                char8 c = s[i];

                switch (c)
                {
                    case '\'':
                        inComment = !inComment;
                        break;
                    case '\\':
                        i++; // escape sequence -- skip next character
                        break;
                    default:
                        if (!inComment && charToFind == c)
                        {
                            return i;
                        }
                        break;
                }
            }
            return -1;
        }

        
        
        static int nativeGetTwoDigitYearMax(int calID)
		{
			Runtime.NotImplemented();
		}
        
        private static bool nativeGetCalendarData(CalendarData data, String localeName, int calendar)
		{
			data.SetupDefaults();
			//NotImplemented();
			return true;
		}

        static int nativeGetCalendars(String localeName, bool useUserOverride, int[] calendars)
		{
			Runtime.NotImplemented();
		}

    }
 }

