using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PkmnFoundations.Structures
{
    public class BattleVideoHeader4
    {
        public BattleVideoHeader4()
        {
        }

        public BattleVideoHeader4(int pid, ulong serial_number, byte[] data)
        {
            if (data.Length != 228) throw new ArgumentException("Battle video header data must be 228 bytes.");

            PID = pid;
            SerialNumber = serial_number;
            Data = data;
        }

        // todo: encapsulate these so calculated fields are always correct
        public int PID;
        public ulong SerialNumber;
        public byte[] Data;

        public ushort Streak
        {
            get
            {
                return BitConverter.ToUInt16(Data, 0xa4);
            }
        }

        public ushort[] Party
        {
            get
            {
                ushort[] result = new ushort[12];
                for (int x = 0; x < result.Length; x++)
                {
                    result[x] = BitConverter.ToUInt16(Data, 0x80 + x * 2);
                }
                return result;
            }
        }

        public byte[] TrainerName
        {
            get
            {
                byte[] result = new byte[16];
                Array.Copy(Data, 0, result, 0, 16);
                return result;
            }
        }

        public BattleVideoMetagames4 Metagame
        {
            get
            {
                return (BattleVideoMetagames4)Data[0xa6];
            }
        }

        public byte Country
        {
            get
            {
                return Data[0x17];
            }
        }

        public byte Region
        {
            get
            {
                return Data[0x18];
            }
        }

        public BattleVideoHeader4 Clone()
        {
            return new BattleVideoHeader4(PID, SerialNumber, Data.ToArray());
        }

        #region ID number calculation
        // Generating battle video IDs:
        //
        // STEP 1: Prepping the Template:
        // Start with an auto-incrementing primary key. Treat as a string of 12
        // decimal digits. Pad with 0s. We call this the Key.
        //
        // The ones place (rightmost) is used to select one of ten fixed 
        // Templates. A Template has 12 (11 useful) digits. They are basically
        // hardcoded random-looking numbers. The rightmost digit of each 
        // template is undefined/unused. The leftmost digit is never 0.
        //
        // The Key’s tens place and hundreds place are added together mod-10 to
        // get a constant which is used to roll each of the digits in the
        // Template by. Here, rolling means adding in mod-10.
        //
        // The Template’s leftmost digit is rolled in mod-9 instead of 10.
        // (While rolling, skip the number 0.) If the value in the 
        // second-leftmost digit wraps around to or past 0, this leftmost digit
        // is rolled one less time than it would be otherwise.
        //
        // Note: There is probably a better way to represent this algorithm
        // that doesn't require special handling for the leftmost digit based
        // on what the second one is doing. It would involve choosing different
        // Template constants and using a different roll formula.
        //
        // STEP 2: Prepping the Key:
        // Remove the tens place from the Key, then shift all the digits
        // EXCEPT the leftmost and rightmost one place to the right to fill the
        // gap. The second leftmost digit is filled with a constant 0.
        // (Example: 123333345678 becomes 102333334568)
        //
        // Note: This handling of the left two digits is an extrapolation on
        // my part. It gives us the maximum possible range of 10-00000-00000
        // through to 99-99999-99999 in a 1:1 mapping.
        //
        // STEP 3: Putting it together:
        // Overwrite the rightmost digit of the Template with the Key’s ones
        // place digit.
        //
        // For each remaining digit, roll the Template *backwards* a number of
        // times equal to the Key’s digit in that place.

        private static byte[][] m_templates = new byte[][]{
            new byte[]{6, 9, 3, 6, 1, 2, 7, 5, 2, 2, 4, 0},
            new byte[]{8, 0, 7, 2, 6, 2, 4, 9, 1, 4, 4, 1},
            new byte[]{1, 4, 9, 7, 3, 8, 5, 7, 6, 4, 7, 2},
            new byte[]{9, 4, 1, 7, 6, 3, 9, 6, 2, 7, 5, 3},
            new byte[]{4, 6, 4, 2, 9, 9, 7, 3, 4, 6, 1, 4},
            new byte[]{9, 5, 1, 0, 9, 7, 8, 6, 6, 3, 5, 5},
            new byte[]{8, 8, 7, 4, 4, 4, 3, 4, 6, 2, 9, 6},
            new byte[]{6, 3, 7, 7, 5, 6, 7, 6, 1, 9, 5, 7},
            new byte[]{2, 6, 7, 2, 3, 2, 4, 5, 8, 9, 7, 8},
            new byte[]{2, 1, 9, 1, 7, 9, 9, 1, 6, 5, 6, 9}
        };

        /// <summary>
        /// Converts a primary key (auto incrementing) into a Battle Video ID.
        /// </summary>
        public static ulong KeyToSerial(ulong key)
        {
            if (key > 899999999999L || key < 0L) throw new ArgumentOutOfRangeException();

            byte[] keyDigits = LongToDigits(key);
            byte[] serialDigits = m_templates[keyDigits[11]].ToArray();

            byte valueShift = 0;
            valueShift += keyDigits[9];
            valueShift += keyDigits[10];
            valueShift %= 10;

            if (valueShift + serialDigits[1] > 9)
                serialDigits[0]--;

            for (int x = 0; x < 11; x++)
            {
                serialDigits[x] += valueShift;
            }

            serialDigits[0] += 9;
            serialDigits[0] -= keyDigits[0];
            serialDigits[0] %= 9;
            if (serialDigits[0] == 0) serialDigits[0] = 9;

            serialDigits[1] %= 10;

            for (int x = 1; x < 10; x++)
            {
                serialDigits[x + 1] += 10;
                serialDigits[x + 1] -= keyDigits[x];
                serialDigits[x + 1] %= 10;
            }

            return DigitsToLong(serialDigits);
        }

        /// <summary>
        /// Converts Battle Video ID back into a primary key.
        /// </summary>
        public static ulong SerialToKey(ulong serial)
        {
            if (serial > 999999999999L || serial < 100000000000L)
                throw new ArgumentOutOfRangeException();

            byte[] serialDigits = LongToDigits(serial);
            byte[] templateDigits = m_templates[serialDigits[11]];

            serialDigits[0] = (byte)(10 + templateDigits[0] - serialDigits[0]);
            for (int x = 1; x < 11; x++)
            {
                serialDigits[x] = (byte)(10 + templateDigits[x] - serialDigits[x]);
            }

            byte valueShift = (byte)(serialDigits[1] % 10);
            if (templateDigits[1] - valueShift >= 0)
                serialDigits[0]--;

            serialDigits[0] += (byte)(9 - valueShift);
            serialDigits[0] %= 9;

            for (int x = 1; x < 11; x++)
            {
                serialDigits[x] += (byte)(10 - valueShift);
                serialDigits[x] %= 10;
            }
            for (int x = 1; x < 10; x++)
            {
                serialDigits[x] = serialDigits[x + 1];
            }

            serialDigits[10] = (byte)((20 - valueShift - serialDigits[9]) % 10);

            return DigitsToLong(serialDigits);
        }

        private static byte[] LongToDigits(ulong value)
        {
            if (value > 999999999999L || value < 0L) throw new ArgumentException();
            byte[] result = new byte[12];
            for (int x = 11; x >= 0; x--)
            {
                result[x] = (byte)(value % 10);
                value /= 10;
            }
            return result;
        }

        private static ulong DigitsToLong(byte[] digits)
        {
            if (digits.Length != 12) throw new ArgumentException();
            ulong result = 0;
            ulong pow = 1;
            for (int x = 11; x >= 0; x--)
            {
                if (digits[x] > 9) throw new ArgumentException();
                result += digits[x] * pow;
                pow *= 10;
            }
            return result;
        }

        #endregion

        public static String FormatSerial(ulong serial)
        {
            String number = serial.ToString("D12");
            String[] split = new String[3];
            split[0] = number.Substring(0, number.Length - 10);
            split[1] = number.Substring(number.Length - 10, 5);
            split[2] = number.Substring(number.Length - 5, 5);
            return String.Join("-", split);
        }
    }

    public enum BattleVideoMetagames4 : byte
    {
        ColosseumSingleNoRestrictions = 0x00,
        ColosseumSingleStandardCup = 0x01,
        ColosseumSingleFancyCup = 0x02,
        ColosseumSingleLittleCup = 0x03,
        ColosseumSingleLightCup = 0x04,
        ColosseumSingleDoubleCup = 0x05,
        ColosseumSingleOtherCup = 0x06,

        ColosseumDoubleNoRestrictions = 0x07,
        ColosseumDoubleStandardCup = 0x08,
        ColosseumDoubleFancyCup = 0x09,
        ColosseumDoubleLittleCup = 0x0a,
        ColosseumDoubleLightCup = 0x0b,
        ColosseumDoubleDoubleCup = 0x0c,
        ColosseumDoubleOtherCup = 0x0d,

        ColosseumMulti = 0x0e,

        BattleTowerSingle = 0x0f,
        BattleTowerDouble = 0x10,
        BattleTowerMulti = 0x11,

        BattleFactoryLv50Single = 0x12,
        BattleFactoryLv50Double = 0x13,
        BattleFactoryLv50Multi = 0x14,

        BattleFactoryOpenSingle = 0x15,
        BattleFactoryOpenDouble = 0x16,
        BattleFactoryOpenMulti = 0x17,

        BattleHallSingle = 0x18,
        BattleHallDouble = 0x19,
        BattleHallMulti = 0x1a,

        BattleCastleSingle = 0x1b,
        BattleCastleDouble = 0x1c,
        BattleCastleMulti = 0x1d,

        BattleArcadeSingle = 0x1e,
        BattleArcadeDouble = 0x1f,
        BattleArcadeMulti = 0x20,

        SearchColosseumSingleNoRestrictions = 0xfa,
        SearchColosseumSingleCupMatch = 0xfb,
        SearchColosseumDoubleNoRestrictions = 0xfc,
        SearchColosseumDoubleCupMatch = 0xfd,
        // fixme: There's a search type 0xfe showing in my logs.
        // https://github.com/mm201/pkmnFoundations/issues/9
        SearchLatest30 = 0xff,
    }

    public enum BattleVideoRankings4 : uint
    {
        None = 0x00000000,
        Colosseum = 0x00000001,
        BattleFrontier = 0x00000002,
    }
}
