/* **********************************************************************************
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *
 * This source code is subject to terms and conditions of the Shared Source License
 * for IronPython. A copy of the license can be found in the License.html file
 * at the root of this distribution. If you can not locate the Shared Source License
 * for IronPython, please send an email to ironpy@microsoft.com.
 * By using this source code in any fashion, you are agreeing to be bound by
 * the terms of the Shared Source License for IronPython.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * **********************************************************************************/

using System;
using System.Text;
using System.Collections;

namespace IronPython.Runtime {
    [PythonType("slice")]
    public sealed class Slice : IComparable, IPythonEquality {
        public readonly object start, stop, step;

        public Slice(object stop) : this(null, stop, null) { }
        public Slice(object start, object stop) : this(start, stop, null) { }
        public Slice(object start, object stop, object step) {
            this.start = start; this.stop = stop; this.step = step;
        }

        [PythonName("indices")]
        public Tuple indices(int len) {
            int istart, istop, istep;
            indices(len, out istart, out istop, out istep);
            return Tuple.MakeTuple(istart, istop, istep);
        }

        [PythonName("indices")]
        public void indices(int len, out int ostart, out int ostop, out int ostep) {
            int count;
            Ops.FixSlice(len, start, stop, step, out ostart, out ostop, out ostep, out count);
        }

        #region Object overrides

        [PythonName("__str__")]
        public override string ToString() {
            return string.Format("slice({0}, {1}, {2})", Ops.StringRepr(start), Ops.StringRepr(stop), Ops.StringRepr(step));
        }

        public override bool Equals(object obj) {
            return Ops.IsTrue(PythonEquals(obj));
        }

        public override int GetHashCode() {
            int hash = 0;
            if (start != null) hash ^= start.GetHashCode();
            if (stop !=null) hash ^= stop.GetHashCode();
            if (step != null) hash ^= step.GetHashCode();
            return hash;
        }

        #endregion

        public int CompareTo(object other) {
            Slice s = other as Slice;
            if (s == null) throw new ArgumentException("expected slice");
            return Ops.CompareArrays(new object[] { start, stop, step }, 3,
                new object[] { s.start, s.stop, s.step }, 3);
        }

        #region IPythonEquality Members

        [PythonName("__hash__")]
        public object PythonGetHashCode() {
            throw Ops.TypeErrorForUnhashableType();
        }

        [PythonName("__eq__")]
        public object PythonEquals(object other) {
            Slice s = other as Slice;
            if (s == null) return false;
            return (Ops.Compare(start, s.start) == 0) &&
                (Ops.Compare(stop, s.stop) == 0) &&
                (Ops.Compare(step, s.step) == 0);
        }

        #endregion

        internal delegate void SliceAssign(int index, object value);

        internal void DoSliceAssign(SliceAssign assign, int size, object value) {
            int ostart, ostop, ostep;
            indices(size, out ostart, out ostop, out ostep);

            if (this.step == null) throw Ops.ValueError("cannot do slice assignment w/ no step");

            DoSliceAssign(assign, ostart, ostop, ostep, value);            
        }

        private static void DoSliceAssign(SliceAssign assign, int start, int stop, int step, object value) {
            int n = (step > 0 ? (stop - start + step - 1) : (stop - start + step + 1)) / step;
            // fast paths, if we know the size then we can
            // do this quickly.
            if (value is IList) {
                ListSliceAssign(assign, start, n, step, value as IList);
            } else if (value is ISequence) {
                SequenceSliceAssign(assign, start, n, step, value as ISequence);
            } else {
                OtherSliceAssign(assign, start, stop, step, value);
            }
        }

        private static void ListSliceAssign(SliceAssign assign, int start, int n, int step, IList lst) {
            if (lst.Count < n) throw Ops.ValueError("too few items in the enumerator. need {0} have {1}", n,lst.Count);
            else if (lst.Count != n) throw Ops.ValueError("too many items in the enumerator need {0} have {1}", n, lst.Count);

            for (int i = 0, index = start; i < n; i++, index += step) {
                assign(index, lst[i]);
            }
        }

        private static void SequenceSliceAssign(SliceAssign assign, int start, int n, int step, ISequence lst) {
            if (lst.GetLength() < n) throw Ops.ValueError("too few items in the enumerator. need {0}", n);
            else if (lst.GetLength() != n) throw Ops.ValueError("too many items in the enumerator need {0}", n);

            for (int i = 0, index = start; i < n; i++, index += step) {
                assign(index, lst[i]);
            }

        }

        private static void OtherSliceAssign(SliceAssign assign, int start, int stop, int step, object value) {
            // get enumerable data into a list, and then
            // do the slice.
            IEnumerator enumerator = Ops.GetEnumerator(value);
            List sliceData = new List();
            while (enumerator.MoveNext()) sliceData.AddNoLock(enumerator.Current);

            DoSliceAssign(assign, start, stop, step, sliceData);
        }
    }
}