﻿using System.Collections.Generic;
using System.Linq;

namespace pycxx
{
    public class PycCode : PycObject
    {
        public const int CoOptimized = 0x1;

        public const int CoNewlocals = 0x2;

        public const int CoVarargs = 0x4;

        public const int CoVarkeywords = 0x8;

        public const int CoNested = 0x10;

        public const int CoGenerator = 0x20;

        public const int CoNofree = 0x40;

        public const int CoGeneratorAllowed = 0x1000;

        public const int CoFutureDivision = 0x2000;

        public const int CoFutureAbsoluteImport = 0x4000;

        public const int CoFutureWithStatement = 0x8000;

        public const int CoFuturePrintFunction = 0x10000;

        public const int CoFutureUnicodeLiterals = 0x20000;

        public const int CoFutureBarryAsBdfl = 0x40000;

        private int _mArgCount;

        private PycTuple _mCellVars = new PycTuple();

        private PycString _mCode = new PycString();

        private PycTuple _mConsts = new PycTuple();

        private PycString _mFileName = new PycString();

        private int _mFirstLine;

        private int _mFlags;

        private PycTuple _mFreeVars = new PycTuple();

        private GlobalsT _mGlobalsUsed = new GlobalsT(); /* Global vars used in this code */

        private int _mKwOnlyArgCount;

        private PycString _mLnTable = new PycString();

        private PycString _mName = new PycString();

        private PycTuple _mNames = new PycTuple();

        private int _mNumLocals, _mStackSize;

        private PycTuple _mVarNames = new PycTuple();

        public PycCode(int type = TypeCode)
            : base(type)
        {
            _mArgCount = 0;
            _mKwOnlyArgCount = 0;
            _mNumLocals = 0;
            _mStackSize = 0;
            _mFlags = 0;
            _mFirstLine = 0;
        }

        public override void Load(PycData stream, PycModule mod)
        {
            if (mod.VerCompare(1, 3) >= 0 && mod.VerCompare(2, 3) < 0) _mArgCount = stream.Get16();
            else if (mod.VerCompare(2, 3) >= 0) _mArgCount = stream.Get32();

            if (mod.MajorVer() >= 3) _mKwOnlyArgCount = stream.Get32();

            if (mod.VerCompare(1, 3) >= 0 && mod.VerCompare(2, 3) < 0) _mNumLocals = stream.Get16();
            else if (mod.VerCompare(2, 3) >= 0) _mNumLocals = stream.Get32();

            if (mod.VerCompare(1, 5) >= 0 && mod.VerCompare(2, 3) < 0) _mStackSize = stream.Get16();
            else if (mod.VerCompare(2, 3) >= 0) _mStackSize = stream.Get32();

            if (mod.VerCompare(1, 3) >= 0 && mod.VerCompare(2, 3) < 0) _mFlags = stream.Get16();
            else if (mod.VerCompare(2, 3) >= 0) _mFlags = stream.Get32();

            _mCode = LoadObject(stream, mod).Cast<PycString>();
            _mConsts = LoadObject(stream, mod).Cast<PycTuple>();
            _mNames = LoadObject(stream, mod).Cast<PycTuple>();

            if (mod.VerCompare(1, 3) >= 0) _mVarNames = LoadObject(stream, mod).Cast<PycTuple>();

            _mFreeVars = mod.VerCompare(2, 1) >= 0 ? LoadObject(stream, mod).Cast<PycTuple>() : null;

            _mCellVars = mod.VerCompare(2, 1) >= 0 ? LoadObject(stream, mod).Cast<PycTuple>() : null;

            _mFileName = LoadObject(stream, mod).Cast<PycString>();
            _mName = LoadObject(stream, mod).Cast<PycString>();

            if (mod.VerCompare(1, 5) >= 0 && mod.VerCompare(2, 3) < 0) _mFirstLine = stream.Get16();
            else if (mod.VerCompare(2, 3) >= 0) _mFirstLine = stream.Get32();

            if (mod.VerCompare(1, 5) >= 0) _mLnTable = LoadObject(stream, mod).Cast<PycString>();
        }

        public int ArgCount()
        {
            return _mArgCount;
        }

        public int KwOnlyArgCount()
        {
            return _mKwOnlyArgCount;
        }

        public int NumLocals()
        {
            return _mNumLocals;
        }

        public int StackSize()
        {
            return _mStackSize;
        }

        public int Flags()
        {
            return _mFlags;
        }

        public PycString Code()
        {
            return _mCode;
        }

        public PycTuple Consts()
        {
            return _mConsts;
        }

        public PycTuple Names()
        {
            return _mNames;
        }

        public PycTuple VarNames()
        {
            return _mVarNames;
        }

        public PycTuple FreeVars()
        {
            return _mFreeVars;
        }

        public PycTuple CellVars()
        {
            return _mCellVars;
        }

        public PycString FileName()
        {
            return _mFileName;
        }

        public PycString Name()
        {
            return _mName;
        }

        public int FirstLine()
        {
            return _mFirstLine;
        }

        public PycString LnTable()
        {
            return _mLnTable;
        }

        public PycObject GetConst(int idx)
        {
            return _mConsts.Get(idx);
        }

        public PycString GetName(int idx)
        {
            return _mNames.Get(idx).Cast<PycString>();
        }

        public PycString GetVarName(int idx)
        {
            return _mVarNames.Get(idx).Cast<PycString>();
        }

        public PycObject GetCellVar(int idx)
        {
            return (idx >= _mCellVars.Size()) ? _mFreeVars.Get(idx - _mCellVars.Size()) : _mCellVars.Get(idx);
        }

        public GlobalsT GetGlobals()
        {
            return _mGlobalsUsed;
        }

        public void MarkGlobal(PycString varname)
        {
            _mGlobalsUsed.Add(varname);
            _mGlobalsUsed = _mGlobalsUsed.unique();
        }

        public class GlobalsT : List<PycString>
        {
            public GlobalsT unique()
            {
                GlobalsT uniqueList = new GlobalsT();
                var distinctList = this.Distinct().ToList();
                for (int i = 0; i < distinctList.Count; i++)
                {
                    uniqueList.Add(distinctList[i]);
                }
                return uniqueList;
            }
        };
    }
}